Add SSL support to P2P

This commit is contained in:
Lee *!* Clagett 2023-09-19 19:05:02 -04:00
parent cc73fe7116
commit 60925f1846
29 changed files with 756 additions and 363 deletions

View File

@ -131,6 +131,7 @@ namespace net_utils
void terminate();
void on_terminating();
bool send(epee::byte_slice message);
bool start_internal(
bool is_income,
@ -296,8 +297,19 @@ namespace net_utils
bool speed_limit_is_enabled() const; ///< tells us should we be sleeping here (e.g. do not sleep on RPC connections)
void set_ssl_enabled()
{
m_state.ssl.enabled = true;
m_state.ssl.handshaked = true;
}
bool cancel();
//! Used by boosted_tcp_server class in async_connect_internal
template<typename F>
auto wrap(F&& f)
{
return m_strand.wrap(std::forward<F>(f));
}
private:
//----------------- i_service_endpoint ---------------------
@ -376,10 +388,11 @@ namespace net_utils
}
bool add_connection(t_connection_context& out, boost::asio::ip::tcp::socket&& sock, network_address real_remote, epee::net_utils::ssl_support_t ssl_support = epee::net_utils::ssl_support_t::e_ssl_support_autodetect);
try_connect_result_t try_connect(connection_ptr new_connection_l, const std::string& adr, const std::string& port, boost::asio::ip::tcp::socket &sock_, const boost::asio::ip::tcp::endpoint &remote_endpoint, const std::string &bind_ip, uint32_t conn_timeout, epee::net_utils::ssl_support_t ssl_support);
bool connect(const std::string& adr, const std::string& port, uint32_t conn_timeot, t_connection_context& cn, const std::string& bind_ip = "0.0.0.0", epee::net_utils::ssl_support_t ssl_support = epee::net_utils::ssl_support_t::e_ssl_support_autodetect);
try_connect_result_t try_connect(connection_ptr new_connection_l, const std::string& adr, const std::string& port, boost::asio::ip::tcp::socket &sock_, const boost::asio::ip::tcp::endpoint &remote_endpoint, const std::string &bind_ip, uint32_t conn_timeout, epee::net_utils::ssl_options_t& ssl_support);
bool connect(const std::string& adr, const std::string& port, uint32_t conn_timeot, t_connection_context& cn, const std::string& bind_ip = "0.0.0.0", epee::net_utils::ssl_options_t ssl_options = epee::net_utils::ssl_support_t::e_ssl_support_autodetect);
template<class t_callback>
bool connect_async(const std::string& adr, const std::string& port, uint32_t conn_timeot, const t_callback &cb, const std::string& bind_ip = "0.0.0.0", epee::net_utils::ssl_support_t ssl_support = epee::net_utils::ssl_support_t::e_ssl_support_autodetect);
bool connect_async(const std::string& adr, const std::string& port, uint32_t conn_timeot, const t_callback &cb, const std::string& bind_ip = "0.0.0.0", epee::net_utils::ssl_options_t ssl_options = epee::net_utils::ssl_support_t::e_ssl_support_autodetect);
boost::asio::ssl::context& get_ssl_context() noexcept
{
@ -477,6 +490,11 @@ namespace net_utils
bool is_thread_worker();
template<typename t_callback>
bool connect_async_internal(const connection_ptr& new_connection_l, const boost::asio::ip::tcp::endpoint& remote_endpoint, uint32_t conn_timeout, const t_callback &cb);
bool remove_connection(const connection_ptr& ptr);
const std::shared_ptr<typename connection<t_protocol_handler>::shared_state> m_state;
/// The io_service used to perform asynchronous operations.

View File

@ -895,7 +895,7 @@ namespace net_utils
boost::uuids::random_generator()(),
*real_remote,
is_income,
connection_basic::m_ssl_support == ssl_support_t::e_ssl_support_enabled
connection_basic::m_ssl_support
);
m_host = real_remote->host_str();
try { host_count(1); } catch(...) { /* ignore */ }
@ -1559,7 +1559,7 @@ namespace net_utils
}
//---------------------------------------------------------------------------------
template<class t_protocol_handler>
typename boosted_tcp_server<t_protocol_handler>::try_connect_result_t boosted_tcp_server<t_protocol_handler>::try_connect(connection_ptr new_connection_l, const std::string& adr, const std::string& port, boost::asio::ip::tcp::socket &sock_, const boost::asio::ip::tcp::endpoint &remote_endpoint, const std::string &bind_ip, uint32_t conn_timeout, epee::net_utils::ssl_support_t ssl_support)
typename boosted_tcp_server<t_protocol_handler>::try_connect_result_t boosted_tcp_server<t_protocol_handler>::try_connect(connection_ptr new_connection_l, const std::string& adr, const std::string& port, boost::asio::ip::tcp::socket &sock_, const boost::asio::ip::tcp::endpoint &remote_endpoint, const std::string &bind_ip, uint32_t conn_timeout, epee::net_utils::ssl_options_t& ssl_options)
{
TRY_ENTRY();
@ -1635,7 +1635,7 @@ namespace net_utils
{
// Handshake
MDEBUG("Handshaking SSL...");
if (!new_connection_l->handshake(boost::asio::ssl::stream_base::client))
if (!new_connection_l->client_handshake(ssl_options))
{
if (ssl_support == epee::net_utils::ssl_support_t::e_ssl_support_autodetect)
{
@ -1649,6 +1649,7 @@ namespace net_utils
sock_.close();
return CONNECT_FAILURE;
}
new_connection_l->set_ssl_enabled();
}
return CONNECT_SUCCESS;
@ -1657,11 +1658,11 @@ namespace net_utils
}
//---------------------------------------------------------------------------------
template<class t_protocol_handler>
bool boosted_tcp_server<t_protocol_handler>::connect(const std::string& adr, const std::string& port, uint32_t conn_timeout, t_connection_context& conn_context, const std::string& bind_ip, epee::net_utils::ssl_support_t ssl_support)
bool boosted_tcp_server<t_protocol_handler>::connect(const std::string& adr, const std::string& port, uint32_t conn_timeout, t_connection_context& conn_context, const std::string& bind_ip, epee::net_utils::ssl_options_t ssl_options)
{
TRY_ENTRY();
connection_ptr new_connection_l(new connection<t_protocol_handler>(io_service_, m_state, m_connection_type, ssl_support) );
connection_ptr new_connection_l(new connection<t_protocol_handler>(io_service_, m_state, m_connection_type, ssl_options.support) );
connections_mutex.lock();
connections_.insert(new_connection_l);
MDEBUG("connections_ size now " << connections_.size());
@ -1746,24 +1747,22 @@ namespace net_utils
//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_to_use, 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_options);
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)
if (ssl_options.support == epee::net_utils::ssl_support_t::e_ssl_support_autodetect && try_connect_result == CONNECT_NO_SSL)
{
// 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_to_use, conn_timeout, epee::net_utils::ssl_support_t::e_ssl_support_disabled);
ssl_options = 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, ssl_options);
if (try_connect_result != CONNECT_SUCCESS)
return false;
}
// start adds the connection to the config object's list, so we don't need to have it locally anymore
connections_mutex.lock();
connections_.erase(new_connection_l);
connections_mutex.unlock();
bool r = new_connection_l->start(false, 1 < m_threads_count);
bool r = remove_connection(new_connection_l) && new_connection_l->start(false, 1 < m_threads_count);
if (r)
{
new_connection_l->get_context(conn_context);
@ -1783,10 +1782,10 @@ namespace net_utils
}
//---------------------------------------------------------------------------------
template<class t_protocol_handler> template<class t_callback>
bool boosted_tcp_server<t_protocol_handler>::connect_async(const std::string& adr, const std::string& port, uint32_t conn_timeout, const t_callback &cb, const std::string& bind_ip, epee::net_utils::ssl_support_t ssl_support)
bool boosted_tcp_server<t_protocol_handler>::connect_async(const std::string& adr, const std::string& port, uint32_t conn_timeout, const t_callback &cb, const std::string& bind_ip, epee::net_utils::ssl_options_t ssl_options)
{
TRY_ENTRY();
connection_ptr new_connection_l(new connection<t_protocol_handler>(io_service_, m_state, m_connection_type, ssl_support) );
connection_ptr new_connection_l(new connection<t_protocol_handler>(io_service_, m_state, m_connection_type, ssl_options.support) );
connections_mutex.lock();
connections_.insert(new_connection_l);
MDEBUG("connections_ size now " << connections_.size());
@ -1863,60 +1862,113 @@ namespace net_utils
return false;
}
}
boost::shared_ptr<boost::asio::deadline_timer> sh_deadline(new boost::asio::deadline_timer(io_service_));
//start deadline
sh_deadline->expires_from_now(boost::posix_time::milliseconds(conn_timeout));
sh_deadline->async_wait([=](const boost::system::error_code& error)
{
if(error != boost::asio::error::operation_aborted)
{
_dbg3("Failed to connect to " << adr << ':' << port << ", because of timeout (" << conn_timeout << ")");
new_connection_l->socket().close();
}
});
//start async connect
sock_.async_connect(remote_endpoint, [=](const boost::system::error_code& ec_)
{
t_connection_context conn_context = AUTO_VAL_INIT(conn_context);
boost::system::error_code ignored_ec;
boost::asio::ip::tcp::socket::endpoint_type lep = new_connection_l->socket().local_endpoint(ignored_ec);
if(!ec_)
{//success
if(!sh_deadline->cancel())
{
cb(conn_context, boost::asio::error::operation_aborted);//this mean that deadline timer already queued callback with cancel operation, rare situation
}else
{
_dbg3("[sock " << new_connection_l->socket().native_handle() << "] Connected success to " << adr << ':' << port <<
" from " << lep.address().to_string() << ':' << lep.port());
// start adds the connection to the config object's list, so we don't need to have it locally anymore
connections_mutex.lock();
connections_.erase(new_connection_l);
connections_mutex.unlock();
bool r = new_connection_l->start(false, 1 < m_threads_count);
if (r)
{
new_connection_l->get_context(conn_context);
cb(conn_context, ec_);
}
else
{
_dbg3("[sock " << new_connection_l->socket().native_handle() << "] Failed to start connection to " << adr << ':' << port);
cb(conn_context, boost::asio::error::fault);
}
}
}else
{
_dbg3("[sock " << new_connection_l->socket().native_handle() << "] Failed to connect to " << adr << ':' << port <<
" from " << lep.address().to_string() << ':' << lep.port() << ": " << ec_.message() << ':' << ec_.value());
cb(conn_context, ec_);
}
});
return true;
ssl_options.configure(new_connection_l->socket_, boost::asio::ssl::stream_base::client);
return connect_async_internal(new_connection_l, remote_endpoint, conn_timeout, cb);
CATCH_ENTRY_L0("boosted_tcp_server<t_protocol_handler>::connect_async", false);
}
template<class t_protocol_handler> template<class t_callback>
bool boosted_tcp_server<t_protocol_handler>::connect_async_internal(const connection_ptr& new_connection_l, const boost::asio::ip::tcp::endpoint& remote_endpoint, uint32_t conn_timeout, const t_callback &cb)
{
if (!new_connection_l)
return false;
TRY_ENTRY();
const auto on_timer = [=](boost::system::error_code error)
{
if (error != boost::asio::error::operation_aborted)
{
_dbg3("Failed to connect to " << remote_endpoint << ", because of timeout (" << conn_timeout << ')');
new_connection_l->socket().close(error); // ignore errors
}
};
auto sh_deadline = std::make_shared<boost::asio::deadline_timer>(io_service_);
sh_deadline->expires_from_now(boost::posix_time::milliseconds(conn_timeout));
sh_deadline->async_wait(new_connection_l->wrap(on_timer));
new_connection_l->socket().async_connect(remote_endpoint, new_connection_l->wrap([=](const boost::system::error_code& ec_)
{
const auto on_cancel = [=](const boost::system::error_code& error)
{
boost::system::error_code ignored_ec{};
const auto lep = new_connection_l->socket().local_endpoint(ignored_ec);
_dbg3("[sock " << new_connection_l->socket().native_handle() << "] to " << remote_endpoint << " from " << lep << " failed: " << error.message());
if (remove_connection(new_connection_l))
cb(t_connection_context{}, error);
};
if(!ec_)
{//success
const auto on_ready = [=] ()
{
if (sh_deadline->cancel())
{
boost::system::error_code ignored_ec{};
const auto lep = new_connection_l->socket().local_endpoint(ignored_ec);
_dbg3("[sock " << new_connection_l->socket().native_handle() << "] Connected successfully to " << remote_endpoint <<
" from " << lep.address().to_string() << ':' << lep.port());
if (remove_connection(new_connection_l) && new_connection_l->start(false, 1 < m_threads_count))
{
t_connection_context conn_context{};
new_connection_l->get_context(conn_context);
cb(conn_context, ec_);
}
else
on_cancel(boost::asio::error::fault);
}
else // if timer already expired
on_cancel(boost::asio::error::operation_aborted);
};
if (new_connection_l->get_ssl_support() != ssl_support_t::e_ssl_support_disabled)
{
// set new timer for handshake
if (sh_deadline->expires_from_now(boost::posix_time::milliseconds(conn_timeout)))
{
sh_deadline->async_wait(new_connection_l->wrap(on_timer));
new_connection_l->socket_.async_handshake(boost::asio::ssl::stream_base::client, new_connection_l->wrap([=] (const boost::system::error_code& ec)
{
if (ec)
{
if (new_connection_l->get_ssl_support() == ssl_support_t::e_ssl_support_autodetect)
{
_dbg3("[sock " << new_connection_l->socket().native_handle() << "] SSL connection to " <<
remote_endpoint << " failed: " << ec.message() << ". Trying without SSL");
new_connection_l->disable_ssl();
connect_async_internal(new_connection_l, remote_endpoint, conn_timeout, cb);
}
else // ssl mandatory and failed
on_cancel(ec);
}
else // ssl handshake complete
on_ready();
}));
}
else // if timer already expired
on_cancel(boost::asio::error::operation_aborted);
}
else // ssl disabled
on_ready();
}
else // ec_ has error
on_cancel(ec_);
}));
return true;
CATCH_ENTRY_L0("boosted_tcp_server<t_protocol_handler>::connect_async_internal", false);
}
template<class t_protocol_handler>
bool boosted_tcp_server<t_protocol_handler>::remove_connection(const connection_ptr& new_connection)
{
if (!new_connection)
return false;
const boost::lock_guard<boost::mutex> sync{connections_mutex};
connections_.erase(new_connection);
return true;
}
} // namespace
} // namespace

View File

@ -132,37 +132,15 @@ class connection_basic { // not-templated base class for rapid developmet of som
ssl_support_t get_ssl_support() const { return m_ssl_support; }
void disable_ssl() { m_ssl_support = epee::net_utils::ssl_support_t::e_ssl_support_disabled; }
bool handshake(boost::asio::ssl::stream_base::handshake_type type, boost::asio::const_buffer buffer = {})
bool client_handshake(ssl_options_t& ssl)
{
return ssl.handshake(socket_, boost::asio::ssl::stream_base::client);
}
bool server_handshake(boost::asio::const_buffer buffer)
{
//m_state != nullptr verified in constructor
return m_state->ssl_options().handshake(socket_, type, buffer);
}
template<typename MutableBufferSequence, typename ReadHandler>
void async_read_some(const MutableBufferSequence &buffers, ReadHandler &&handler)
{
if (m_ssl_support == epee::net_utils::ssl_support_t::e_ssl_support_enabled)
socket_.async_read_some(buffers, std::forward<ReadHandler>(handler));
else
socket().async_read_some(buffers, std::forward<ReadHandler>(handler));
}
template<typename ConstBufferSequence, typename WriteHandler>
void async_write_some(const ConstBufferSequence &buffers, WriteHandler &&handler)
{
if (m_ssl_support == epee::net_utils::ssl_support_t::e_ssl_support_enabled)
socket_.async_write_some(buffers, std::forward<WriteHandler>(handler));
else
socket().async_write_some(buffers, std::forward<WriteHandler>(handler));
}
template<typename ConstBufferSequence, typename WriteHandler>
void async_write(const ConstBufferSequence &buffers, WriteHandler &&handler)
{
if (m_ssl_support == epee::net_utils::ssl_support_t::e_ssl_support_enabled)
boost::asio::async_write(socket_, buffers, std::forward<WriteHandler>(handler));
else
boost::asio::async_write(socket(), buffers, std::forward<WriteHandler>(handler));
return m_state->ssl_options().handshake(socket_, boost::asio::ssl::stream_base::server, buffer);
}
// various handlers to be called from connection class:

View File

@ -130,7 +130,7 @@ namespace levin
//! Provides space for levin (p2p) header, so that payload can be sent without copy
class message_writer
{
byte_slice finalize(uint32_t command, uint32_t flags, uint32_t return_code, bool expect_response);
byte_slice finalize(uint32_t command, uint32_t flags, uint32_t return_code, bool expect_response, bool pad);
public:
using header = bucket_head2;
@ -147,12 +147,13 @@ namespace levin
{
return buffer.size() < sizeof(header) ? 0 : buffer.size() - sizeof(header);
}
byte_slice finalize_invoke(uint32_t command) { return finalize(command, LEVIN_PACKET_REQUEST, 0, true); }
byte_slice finalize_notify(uint32_t command) { return finalize(command, LEVIN_PACKET_REQUEST, 0, false); }
byte_slice finalize_response(uint32_t command, uint32_t return_code)
// `pad == true` will add 0-8192 of zero bytes (actual amount randomized)
byte_slice finalize_invoke(uint32_t command, bool pad) { return finalize(command, LEVIN_PACKET_REQUEST, 0, true, pad); }
byte_slice finalize_notify(uint32_t command, bool pad) { return finalize(command, LEVIN_PACKET_REQUEST, 0, false, pad); }
byte_slice finalize_response(uint32_t command, uint32_t return_code, bool pad)
{
return finalize(command, LEVIN_PACKET_RESPONSE, return_code, false);
return finalize(command, LEVIN_PACKET_RESPONSE, return_code, false, pad);
}
//! Has space for levin header until a finalize method is used

View File

@ -486,7 +486,7 @@ public:
bool is_response = (m_oponent_protocol_ver == LEVIN_PROTOCOL_VER_1 && m_current_head.m_flags&LEVIN_PACKET_RESPONSE);
MDEBUG(m_connection_context << "LEVIN_PACKET_RECEIVED. [len=" << m_current_head.m_cb
MDEBUG(m_connection_context << "LEVIN_PACKET_RECEIVED. [len=" << m_current_head.m_cb
<< ", flags" << m_current_head.m_flags
<< ", r?=" << m_current_head.m_have_to_return_data
<<", cmd = " << m_current_head.m_command
@ -526,7 +526,7 @@ public:
if (m_current_head.m_command == m_connection_context.handshake_command() && m_connection_context.handshake_complete())
m_max_packet_size = m_config.m_max_packet_size;
if(!send_message(return_message.finalize_response(m_current_head.m_command, return_code)))
if(!send_message(return_message.finalize_response(m_current_head.m_command, return_code, m_connection_context.should_pad())))
return false;
}
else
@ -620,7 +620,7 @@ public:
if (command == m_connection_context.handshake_command())
m_max_packet_size = m_config.m_max_packet_size;
if(!send_message(in_msg.finalize_invoke(command)))
if(!send_message(in_msg.finalize_invoke(command, m_connection_context.should_pad())))
{
LOG_ERROR_CC(m_connection_context, "Failed to do_send");
err_code = LEVIN_ERROR_CONNECTION;

View File

@ -46,7 +46,7 @@ namespace epee
namespace net_utils
{
enum class ssl_support_t: uint8_t {
e_ssl_support_disabled,
e_ssl_support_disabled = 0,
e_ssl_support_enabled,
e_ssl_support_autodetect,
};
@ -107,6 +107,9 @@ namespace net_utils
//! \return True if `host` can be verified using `this` configuration WITHOUT system "root" CAs.
bool has_strong_verification(boost::string_ref host) const noexcept;
//! \return All fingerprints
const std::vector<std::vector<std::uint8_t>>& fingerprints() const noexcept { return fingerprints_; }
//! Search against internal fingerprints. Always false if `behavior() != user_certificate_check`.
bool has_fingerprint(boost::asio::ssl::verify_context &ctx) const;
@ -151,6 +154,30 @@ namespace net_utils
bool create_ec_ssl_certificate(EVP_PKEY *&pkey, X509 *&cert);
bool create_rsa_ssl_certificate(EVP_PKEY *&pkey, X509 *&cert);
std::vector<std::uint8_t> convert_fingerprint(const boost::string_ref id);
/**
* @brief Create a binary X509 certificate fingerprint
*
* @param[in] cert The certificate which will be used to create the fingerprint
* @param[in] fdig The digest algorithm to use, defaults to SHA-256 b/c that is what ssl_options_t uses
* @return The binary fingerprint string
*
* @throw boost::system_error if there is an OpenSSL error
*/
std::string get_ssl_fingerprint(const X509 *cert, const EVP_MD *fdig = EVP_sha256());
/**
* @brief Create a binary X509 certificate fingerprint
*
* @param[in] context The context for the current certificate.
* @param[in] fdig The digest algorithm to use, defaults to SHA-256 b/c that is what ssl_options_t uses
* @return The binary fingerprint string
*
* @throw boost::system_error if there is an OpenSSL error
*/
std::string get_ssl_fingerprint(boost::asio::ssl::context& context, const EVP_MD *fdig = EVP_sha256());
/**
* @brief Create a human-readable X509 certificate fingerprint
*

View File

@ -32,11 +32,13 @@
#include <boost/uuid/uuid.hpp>
#include <boost/asio/io_service.hpp>
#include <boost/asio/ip/address_v6.hpp>
#include <boost/optional/optional.hpp>
#include <typeinfo>
#include <type_traits>
#include "byte_slice.h"
#include "enums.h"
#include "misc_log_ex.h"
#include "net_ssl.h"
#include "serialization/keyvalue_serialization.h"
#include "int-util.h"
@ -367,7 +369,7 @@ namespace net_utils
const network_address m_remote_address;
const bool m_is_income;
const time_t m_started;
const bool m_ssl;
const ssl_support_t m_ssl;
time_t m_last_recv;
time_t m_last_send;
uint64_t m_recv_cnt;
@ -378,7 +380,7 @@ namespace net_utils
double m_max_speed_up;
connection_context_base(boost::uuids::uuid connection_id,
const network_address &remote_address, bool is_income, bool ssl,
const network_address &remote_address, bool is_income, ssl_support_t ssl,
time_t last_recv = 0, time_t last_send = 0,
uint64_t recv_cnt = 0, uint64_t send_cnt = 0):
m_connection_id(connection_id),
@ -400,7 +402,7 @@ namespace net_utils
m_remote_address(),
m_is_income(false),
m_started(time(NULL)),
m_ssl(false),
m_ssl(ssl_support_t::e_ssl_support_disabled),
m_last_recv(0),
m_last_send(0),
m_recv_cnt(0),
@ -421,11 +423,18 @@ namespace net_utils
set_details(a.m_connection_id, a.m_remote_address, a.m_is_income, a.m_ssl);
return *this;
}
bool should_pad() const
{
if (m_remote_address.get_zone() == zone::public_)
return m_ssl != ssl_support_t::e_ssl_support_disabled;
return true; // all other zones use encryption by default
}
private:
template<class t_protocol_handler>
friend class connection;
void set_details(boost::uuids::uuid connection_id, const network_address &remote_address, bool is_income, bool ssl)
void set_details(boost::uuids::uuid connection_id, const network_address &remote_address, bool is_income, ssl_support_t ssl)
{
this->~connection_context_base();
new(this) connection_context_base(connection_id, remote_address, is_income, ssl);

View File

@ -111,7 +111,8 @@ namespace epee
levin::message_writer to_send;
stg.store_to_binary(to_send.buffer);
int res = transport.send(to_send.finalize_notify(command), conn_id);
const bool ssl = (context.m_ssl != ssl_support_t::e_ssl_support_disabled);
int res = transport.send(to_send.finalize_notify(command, ssl), conn_id);
if(res <=0 )
{
MERROR("Failed to notify command " << command << " return code " << res);

View File

@ -70,6 +70,7 @@ target_link_libraries(epee
${Boost_REGEX_LIBRARY}
${Boost_SYSTEM_LIBRARY}
${OPENSSL_LIBRARIES}
${sodium_LIBRARIES}
PRIVATE
${EXTRA_LIBRARIES})

View File

@ -28,6 +28,8 @@
#include "net/levin_base.h"
#include <sodium/randombytes.h>
#include "cryptonote_config.h"
#include "int-util.h"
namespace epee
@ -41,15 +43,25 @@ namespace levin
buffer.put_n(0, sizeof(header));
}
byte_slice message_writer::finalize(const uint32_t command, const uint32_t flags, const uint32_t return_code, const bool expect_response)
byte_slice message_writer::finalize(const uint32_t command, const uint32_t flags, const uint32_t return_code, const bool expect_response, const bool pad)
{
if (buffer.size() < sizeof(header))
throw std::runtime_error{"levin_writer::finalize already called"};
header head = make_header(command, payload_size(), flags, expect_response);
std::uint32_t pad_bytes = 0;
if (pad)
{
static_assert(P2P_MAX_LEVIN_PAD_BYTES <= std::numeric_limits<std::uint32_t>::max(), "invalid max ssl pad bytes");
pad_bytes = randombytes_uniform(P2P_MAX_LEVIN_PAD_BYTES);
if (std::numeric_limits<std::size_t>::max() - payload_size() < pad_bytes)
throw std::runtime_error{"levin_write::finalize failed to pad bytes due to overflow"};
}
header head = make_header(command, payload_size() + pad_bytes, flags, expect_response);
head.m_return_code = SWAP32LE(return_code);
std::memcpy(buffer.tellp() - buffer.size(), std::addressof(head), sizeof(head));
buffer.put_n(0, pad_bytes);
return byte_slice{std::move(buffer)};
}
@ -92,12 +104,13 @@ namespace levin
will ignore extra bytes. So just pad with zeroes and otherwise send
a "normal", not fragmented message. */
// do not use randomized padding, this must be to message boundary
message.buffer.put_n(0, noise_size - message.buffer.size());
return message.finalize_notify(command);
return message.finalize_notify(command, false);
}
// fragment message
const byte_slice payload_bytes = message.finalize_notify(command);
const byte_slice payload_bytes = message.finalize_notify(command, false);
span<const std::uint8_t> payload = to_span(payload_bytes);
const size_t payload_space = noise_size - sizeof(bucket_head2);

View File

@ -27,6 +27,7 @@
// 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.
#include <algorithm>
#include <string.h>
#include <thread>
#include <boost/asio/ssl.hpp>
@ -521,18 +522,19 @@ void ssl_options_t::configure(
{
socket.set_verify_mode(boost::asio::ssl::verify_peer | boost::asio::ssl::verify_fail_if_no_peer_cert);
socket.set_verify_callback([&](const bool preverified, boost::asio::ssl::verify_context &ctx)
// callback occurs after `configure` has exited, so keep copies and not references
const ssl_options_t this_copy(*this);
socket.set_verify_callback([host, this_copy = std::move(this_copy)](const bool preverified, boost::asio::ssl::verify_context &ctx)
{
// preverified means it passed system or user CA check. System CA is never loaded
// when fingerprints are whitelisted.
const bool verified = preverified &&
(verification != ssl_verification_t::system_ca || host.empty() || boost::asio::ssl::rfc2818_verification(host)(preverified, ctx));
(this_copy.verification != ssl_verification_t::system_ca || host.empty() || boost::asio::ssl::rfc2818_verification(host)(preverified, ctx));
if (!verified && !has_fingerprint(ctx))
if (!verified && !this_copy.has_fingerprint(ctx))
{
// autodetect will reconnect without SSL - warn and keep connection encrypted
if (support != ssl_support_t::e_ssl_support_autodetect)
if (this_copy.support != ssl_support_t::e_ssl_support_autodetect)
{
MERROR("SSL certificate is not in the allowed list, connection dropped");
return false;
@ -644,6 +646,44 @@ bool ssl_options_t::handshake(
return true;
}
std::vector<std::uint8_t> convert_fingerprint(const boost::string_ref id)
{
std::vector<std::uint8_t> out;
out.resize(id.size());
std::copy(id.begin(), id.end(), out.begin());
return out;
}
std::string get_ssl_fingerprint(const X509 *cert, const EVP_MD *fdig)
{
CHECK_AND_ASSERT_THROW_MES(cert && fdig, "Pointer args to get_ssl_fingerprint cannot be null");
std::string md;
unsigned n = 0;
md.resize(EVP_MAX_MD_SIZE);
if (!X509_digest(cert, fdig, reinterpret_cast<std::uint8_t*>(&md[0]), &n))
{
const unsigned long ssl_err_val = static_cast<int>(ERR_get_error());
const boost::system::error_code ssl_err_code = boost::asio::error::ssl_errors(static_cast<int>(ssl_err_val));
MERROR("Failed to create SSL fingerprint: " << ERR_reason_error_string(ssl_err_val));
throw boost::system::system_error(ssl_err_code, ERR_reason_error_string(ssl_err_val));
}
md.resize(n);
return md;
}
std::string get_ssl_fingerprint(boost::asio::ssl::context &ssl_context, const EVP_MD *fdig)
{
SSL_CTX *ctx = ssl_context.native_handle();
CHECK_AND_ASSERT_THROW_MES(ctx, "Failed to get SSL context");
X509* ssl_cert = nullptr;
std::unique_ptr<SSL, decltype(&SSL_free)> dflt_SSL(SSL_new(ctx), SSL_free);
CHECK_AND_ASSERT_THROW_MES(dflt_SSL, "Failed to create new SSL object");
CHECK_AND_ASSERT_THROW_MES((ssl_cert = SSL_get_certificate(dflt_SSL.get())), "Failed to get SSL certificate");
return get_ssl_fingerprint(ssl_cert, fdig);
}
std::string get_hr_ssl_fingerprint(const X509 *cert, const EVP_MD *fdig)
{
unsigned int j;

View File

@ -135,6 +135,50 @@ the `Expect Response` field zeroed. When a message of this type is received, the
contents can be safely ignored.
## Encryption
The Levin protocol now has optional SSL encryption. Each peer can have 4 states:
(1) no encryption, (2) autodetect encryption, (3) mandatory encryption with no
certificate verification, or (4) mandatory encryption with certificate
verification.
When a peerlist is shared, the encryption information is ignored (not
trusted), and is put into `autodetect` mode by default. The node data shared
during handshakes OR pings can change the node state to `no encryption` or
`authenticated encryption`, based on direction information from the peer.
> The server/responder must always be in autodetect SSL mode, so that it can
handle any possible state that the client/initiator is in.
> If a node changes certificates, "old" peers will be unable to connect until
the node is completely purged from the white/gray lists OR until the node makes
a direct connection to provide the new encryption state. Incoming connections
from "new" peers (that never saw the old certificate) will still be possible.
### No Encryption
If a peer chooses to have no encryption, it will send `encryption_mode = 1` in
Handshake messages. All clients/initiators can then skip an attempt to connect
via SSL.
### Autodetect Encryption
All peers start in this state, and only move to `no encryption` or
`authenticated encryption` when: (1) the peer is listed on the CLI explicitly
requesting one of the other states, or (2) the peer sends a handshake or ping
message indicating it should be in another state.
When connecting to a peer of this type, the client/initiator should attempt
SSL with no server/responder verification. If the connection fails, it should
immediately attempt a non-SSL connection.
### Authenticated Encryption
If a peer chooses to have a persistent SSL certificate, it will send
`encryption_ver = 2` AND `ssl_finger = <binary sha256 of cert>` in the
Handshake messages.
When connecting to a peer of this type, the client/initiator should attempt
a SSL connection with verification of the SSL fingerprint. The connection
should fail if the fingerprint check fails - it is treated identically to
a invalid/old port number.
## Commands
### P2P (Admin) Commands

View File

@ -147,6 +147,7 @@
#define P2P_DEFAULT_SYNC_SEARCH_CONNECTIONS_COUNT 2
#define P2P_DEFAULT_LIMIT_RATE_UP 2048 // kB/s
#define P2P_DEFAULT_LIMIT_RATE_DOWN 8192 // kB/s
#define P2P_MAX_LEVIN_PAD_BYTES 8192
#define P2P_FAILED_ADDR_FORGET_SECONDS (60*60) //1 hour
#define P2P_IP_BLOCKTIME (60*60*24) //24 hour

View File

@ -34,6 +34,7 @@
#include "serialization/keyvalue_serialization.h"
#include "cryptonote_basic/cryptonote_basic.h"
#include "cryptonote_basic/blobdatatype.h"
#include "net/net_ssl.h"
namespace cryptonote
{
@ -49,7 +50,7 @@ namespace cryptonote
bool incoming;
bool localhost;
bool local_ip;
bool ssl;
epee::net_utils::ssl_support_t ssl;
std::string address;
std::string host;
@ -87,9 +88,11 @@ namespace cryptonote
uint8_t address_type;
BEGIN_KV_SERIALIZE_MAP()
uint8_t ssl_i = uint8_t(ssl);
KV_SERIALIZE(incoming)
KV_SERIALIZE(localhost)
KV_SERIALIZE(local_ip)
epee::serialization::selector<is_store>::serialize(ssl_i, stg, hparent_section, "ssl");
KV_SERIALIZE(address)
KV_SERIALIZE(host)
KV_SERIALIZE(ip)
@ -112,6 +115,7 @@ namespace cryptonote
KV_SERIALIZE(height)
KV_SERIALIZE(pruning_seed)
KV_SERIALIZE(address_type)
ssl = epee::net_utils::ssl_support_t(ssl_i);
END_KV_SERIALIZE_MAP()
};

View File

@ -159,40 +159,12 @@ namespace levin
return get_out_connections(p2p, get_blockchain_height(p2p, core));
}
epee::levin::message_writer make_tx_message(std::vector<blobdata>&& txs, const bool pad, const bool fluff)
epee::levin::message_writer make_tx_message(std::vector<blobdata>&& txs, const bool fluff)
{
NOTIFY_NEW_TRANSACTIONS::request request{};
request.txs = std::move(txs);
request.dandelionpp_fluff = fluff;
if (pad)
{
size_t bytes = 9 /* header */ + 4 /* 1 + 'txs' */ + tools::get_varint_data(request.txs.size()).size();
for(auto tx_blob_it = request.txs.begin(); tx_blob_it!=request.txs.end(); ++tx_blob_it)
bytes += tools::get_varint_data(tx_blob_it->size()).size() + tx_blob_it->size();
// stuff some dummy bytes in to stay safe from traffic volume analysis
static constexpr const size_t granularity = 1024;
size_t padding = granularity - bytes % granularity;
const size_t overhead = 2 /* 1 + '_' */ + tools::get_varint_data(padding).size();
if (overhead > padding)
padding = 0;
else
padding -= overhead;
request._ = std::string(padding, ' ');
epee::byte_slice arg_buff;
epee::serialization::store_t_to_binary(request, arg_buff);
// we probably lowballed the payload size a bit, so added a but too much. Fix this now.
size_t remove = arg_buff.size() % granularity;
if (remove > request._.size())
request._.clear();
else
request._.resize(request._.size() - remove);
// if the size of _ moved enough, we might lose byte in size encoding, we don't care
}
epee::levin::message_writer out;
if (!epee::serialization::store_t_to_binary(request, out.buffer))
throw std::runtime_error{"Failed to serialize to epee binary format"};
@ -202,7 +174,7 @@ namespace levin
bool make_payload_send_txs(connections& p2p, std::vector<blobdata>&& txs, const boost::uuids::uuid& destination, const bool pad, const bool fluff)
{
epee::byte_slice blob = make_tx_message(std::move(txs), pad, fluff).finalize_notify(NOTIFY_NEW_TRANSACTIONS::ID);
epee::byte_slice blob = make_tx_message(std::move(txs), fluff).finalize_notify(NOTIFY_NEW_TRANSACTIONS::ID, pad);
return p2p.send(std::move(blob), destination);
}
@ -847,7 +819,7 @@ namespace levin
// Padding is not useful when using noise mode. Send as stem so receiver
// forwards in Dandelion++ mode.
epee::byte_slice message = epee::levin::make_fragmented_notify(
zone_->noise.size(), NOTIFY_NEW_TRANSACTIONS::ID, make_tx_message(std::move(txs), false, false)
zone_->noise.size(), NOTIFY_NEW_TRANSACTIONS::ID, make_tx_message(std::move(txs), false)
);
if (CRYPTONOTE_MAX_FRAGMENTS * zone_->noise.size() < message.size())
{

View File

@ -653,7 +653,7 @@ bool t_rpc_command_executor::print_connections() {
tools::msg_writer() << std::setw(host_field_width) << std::left << "Remote Host"
<< std::setw(8) << "Type"
<< std::setw(6) << "SSL"
<< std::setw(13) << "SSL"
<< std::setw(20) << "Peer id"
<< std::setw(20) << "Support Flags"
<< std::setw(30) << "Recv/Sent (inactive,sec)"
@ -664,6 +664,20 @@ bool t_rpc_command_executor::print_connections() {
<< std::setw(10) << "Up (kB/s)"
<< std::setw(13) << "Up(now)"
<< std::endl;
const auto ssl_string = [] (const epee::net_utils::ssl_support_t ssl) -> const char*
{
switch (ssl)
{
default:
case epee::net_utils::ssl_support_t::e_ssl_support_disabled:
break;
case epee::net_utils::ssl_support_t::e_ssl_support_autodetect:
return "autodetect";
case epee::net_utils::ssl_support_t::e_ssl_support_enabled:
return "verified";
}
return "no";
};
for (auto & info : res.connections)
{
@ -674,7 +688,7 @@ bool t_rpc_command_executor::print_connections() {
//<< std::setw(30) << std::left << in_out
<< std::setw(host_field_width) << std::left << address
<< std::setw(8) << (get_address_type_name((epee::net_utils::address_type)info.address_type))
<< std::setw(6) << (info.ssl ? "yes" : "no")
<< std::setw(13) << ssl_string(info.ssl)
<< std::setw(20) << info.peer_id
<< std::setw(20) << info.support_flags
<< std::setw(30) << std::to_string(info.recv_count) + "(" + std::to_string(info.recv_idle_time) + ")/" + std::to_string(info.send_count) + "(" + std::to_string(info.send_idle_time) + ")"

View File

@ -142,17 +142,23 @@ namespace nodetool
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"};
const command_line::arg_descriptor<std::vector<std::string> > arg_p2p_add_priority_node = {"add-priority-node", "Specify list of peers to connect to and attempt to keep the connection open"};
const command_line::arg_descriptor<std::vector<std::string> > arg_p2p_add_exclusive_node = {"add-exclusive-node", "Specify list of peers to connect to only."
" If this option is given the options add-priority-node and seed-node are ignored"};
const command_line::arg_descriptor<std::vector<std::string> > arg_p2p_seed_node = {"seed-node", "Connect to a node to retrieve peer addresses, and disconnect"};
const command_line::arg_descriptor<std::vector<std::string> > arg_p2p_add_peer = {"add-peer", "Manually add peer to local peerlist. Append \",sha-256 fingerprint\" to make authenticated connection."
"Append \",no_encryption\" to disable SSL autodetect."};
const command_line::arg_descriptor<std::vector<std::string> > arg_p2p_add_priority_node = {"add-priority-node", "Specify list of peers to connect to and attempt to keep the connection open. "
"Append \",sha-256 fingerprint\" to make authenticated connection. Append \",no_encryption\" to disable SSL autodetect."};
const command_line::arg_descriptor<std::vector<std::string> > arg_p2p_add_exclusive_node = {"add-exclusive-node", "Specify list of peers to connect to only. "
" If this option is given the options add-priority-node and seed-node are ignored. "
"Append \",sha-256 fingerprint\" to make authenticated connection. Append \",no_encryption\" to disable SSL autodetect."};
const command_line::arg_descriptor<std::vector<std::string> > arg_p2p_seed_node = {"seed-node", "Connect to a node to retrieve peer addresses, and disconnect. "
"Append \",sha-256 fingerprint\" to make authenticated connection. Append \",no_encryption\" to disable SSL autodetect."};
const command_line::arg_descriptor<std::vector<std::string> > arg_tx_proxy = {"tx-proxy", "Send local txes through proxy: <network-type>,<socks-ip:port>[,max_connections][,disable_noise] i.e. \"tor,127.0.0.1:9050,100,disable_noise\""};
const command_line::arg_descriptor<std::vector<std::string> > arg_anonymous_inbound = {"anonymous-inbound", "<hidden-service-address>,<[bind-ip:]port>[,max_connections] i.e. \"x.onion,127.0.0.1:18083,100\""};
const command_line::arg_descriptor<std::string> arg_ban_list = {"ban-list", "Specify ban list file, one IP address per line"};
const command_line::arg_descriptor<bool> arg_p2p_hide_my_port = {"hide-my-port", "Do not announce yourself as peerlist candidate", false, true};
const command_line::arg_descriptor<bool> arg_no_sync = {"no-sync", "Don't synchronize the blockchain with other peers", false};
const command_line::arg_descriptor<bool> arg_enable_dns_blocklist = {"enable-dns-blocklist", "Apply realtime blocklist from DNS", false};
const command_line::arg_descriptor<bool> arg_p2p_disable_encryption = {"p2p-disable-encryption", "Disable all P2P encryption", false};
const command_line::arg_descriptor<bool> arg_p2p_persistent_cert = {"p2p-persistent-cert", "Persist p2p SSL certificate across monerod runs to authenticate/identify server", false};
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"};
@ -167,8 +173,10 @@ namespace nodetool
const command_line::arg_descriptor<int64_t> arg_limit_rate = {"limit-rate", "set limit-rate [kB/s]", -1};
const command_line::arg_descriptor<bool> arg_pad_transactions = {
"pad-transactions", "Pad relayed transactions to help defend against traffic volume analysis", false
"pad-transactions", "Pad relayed transactions to help defend against traffic volume analysis. Always enabled on SSL, Tor, and I2P connections.", false
};
const command_line::arg_descriptor<uint32_t> arg_max_connections_per_ip = {"max-connections-per-ip", "Maximum number of connections allowed from the same IP address", 1};
boost::optional<std::vector<proxy>> get_proxies(boost::program_options::variables_map const& vm)

View File

@ -110,17 +110,58 @@ namespace nodetool
struct p2p_connection_context_t: base_type //t_payload_net_handler::connection_context //public net_utils::connection_context_base
{
p2p_connection_context_t()
: peer_id(0),
: cert_finger(),
peer_id(0),
support_flags(0),
encryption_mode(emode_ssl_autodetect),
m_in_timedsync(false)
{}
std::string cert_finger;
peerid_type peer_id;
uint32_t support_flags;
uint8_t encryption_mode;
bool m_in_timedsync;
std::set<epee::net_utils::network_address> sent_addresses;
};
struct p2p_address
{
epee::net_utils::network_address na;
epee::net_utils::ssl_options_t ssl = epee::net_utils::ssl_support_t::e_ssl_support_autodetect;
template<typename T>
void update_peer(T& peer) const
{
peer.adr = na;
peer.encryption_mode = emode_ssl_autodetect;
if (!ssl)
peer.encryption_mode = emode_disabled;
if (!ssl.fingerprints().empty())
{
peer.encryption_mode = emode_ssl_enabled;
auto& fingerprint = ssl.fingerprints().front();
peer.cert_finger.resize(fingerprint.size());
std::copy(fingerprint.begin(), fingerprint.end(), peer.cert_finger.begin());
}
}
bool operator==(const p2p_address& rhs) const
{ return na == rhs.na; }
};
struct string_address
{
std::string address;
epee::net_utils::ssl_options_t ssl;
bool operator<(const string_address& rhs) const noexcept
{ return address < rhs.address; }
bool operator==(const string_address& rhs) const noexcept
{ return address == rhs.address; }
};
template<class t_payload_net_handler>
class node_server: public epee::levin::levin_commands_handler<p2p_connection_context_t<typename t_payload_net_handler::connection_context> >,
public i_p2p_endpoint<typename t_payload_net_handler::connection_context>,
@ -139,7 +180,7 @@ namespace nodetool
typedef epee::net_utils::boosted_tcp_server<epee::levin::async_protocol_handler<p2p_connection_context>> net_server;
struct network_zone;
using connect_func = boost::optional<p2p_connection_context>(network_zone&, epee::net_utils::network_address const&, epee::net_utils::ssl_support_t);
using connect_func = boost::optional<p2p_connection_context>(network_zone&, p2p_address const&);
struct config_t
{
@ -165,6 +206,7 @@ namespace nodetool
m_bind_ipv6_address(),
m_port(),
m_port_ipv6(),
m_ssl_finger(),
m_notifier(),
m_our_address(),
m_peerlist(),
@ -172,6 +214,7 @@ namespace nodetool
m_proxy_address(),
m_current_number_of_out_peers(0),
m_current_number_of_in_peers(0),
m_ssl_support(epee::net_utils::ssl_support_t::e_ssl_support_disabled),
m_seed_nodes_lock(),
m_can_pingback(false),
m_seed_nodes_initialized(false)
@ -187,6 +230,7 @@ namespace nodetool
m_bind_ipv6_address(),
m_port(),
m_port_ipv6(),
m_ssl_finger(),
m_notifier(),
m_our_address(),
m_peerlist(),
@ -194,6 +238,7 @@ namespace nodetool
m_proxy_address(),
m_current_number_of_out_peers(0),
m_current_number_of_in_peers(0),
m_ssl_support(epee::net_utils::ssl_support_t::e_ssl_support_disabled),
m_seed_nodes_lock(),
m_can_pingback(false),
m_seed_nodes_initialized(false)
@ -203,11 +248,12 @@ namespace nodetool
connect_func* m_connect;
net_server m_net_server;
std::vector<epee::net_utils::network_address> m_seed_nodes;
std::vector<p2p_address> m_seed_nodes;
std::string m_bind_ip;
std::string m_bind_ipv6_address;
std::string m_port;
std::string m_port_ipv6;
std::string m_ssl_finger;
cryptonote::levin::notify m_notifier;
epee::net_utils::network_address m_our_address; // in anonymity networks
peerlist_manager m_peerlist;
@ -215,6 +261,7 @@ namespace nodetool
boost::asio::ip::tcp::endpoint m_proxy_address;
std::atomic<unsigned int> m_current_number_of_out_peers;
std::atomic<unsigned int> m_current_number_of_in_peers;
epee::net_utils::ssl_support_t m_ssl_support;
boost::shared_mutex m_seed_nodes_lock;
bool m_can_pingback;
bool m_seed_nodes_initialized;
@ -370,7 +417,7 @@ namespace nodetool
bool make_new_connection_from_anchor_peerlist(const std::vector<anchor_peerlist_entry>& anchor_peerlist);
bool make_new_connection_from_peerlist(network_zone& zone, bool use_white_list);
bool try_to_connect_and_handshake_with_new_peer(const epee::net_utils::network_address& na, bool just_take_peerlist = false, uint64_t last_seen_stamp = 0, PeerType peer_type = white, uint64_t first_seen_stamp = 0);
bool try_to_connect_and_handshake_with_new_peer(const p2p_address& peer, bool just_take_peerlist = false, uint64_t last_seen_stamp = 0, PeerType peer_type = white, uint64_t first_seen_stamp = 0);
size_t get_random_index_with_fixed_probability(size_t max_index);
bool is_peer_used(const peerlist_entry& peer);
bool is_peer_used(const anchor_peerlist_entry& peer);
@ -390,9 +437,9 @@ namespace nodetool
void record_addr_failed(const epee::net_utils::network_address& addr);
bool is_addr_recently_failed(const epee::net_utils::network_address& addr);
bool is_priority_node(const epee::net_utils::network_address& na);
std::set<std::string> get_ip_seed_nodes() const;
std::set<std::string> get_dns_seed_nodes();
std::set<std::string> get_seed_nodes(epee::net_utils::zone);
std::set<string_address> get_ip_seed_nodes() const;
std::set<string_address> get_dns_seed_nodes();
std::set<string_address> get_seed_nodes(epee::net_utils::zone);
bool connect_to_seed(epee::net_utils::zone);
template <class Container>
@ -415,7 +462,7 @@ namespace nodetool
size_t get_outgoing_connections_count();
size_t get_outgoing_connections_count(network_zone&);
bool check_connection_and_handshake_with_peer(const epee::net_utils::network_address& na, uint64_t last_seen_stamp);
bool check_connection_and_handshake_with_peer(const p2p_address& peer, uint64_t last_seen_stamp);
bool gray_peerlist_housekeeping();
bool check_incoming_connections();
@ -474,16 +521,16 @@ namespace nodetool
epee::math_helper::once_a_time_seconds<3600, false> m_incoming_connections_interval;
epee::math_helper::once_a_time_seconds<7000> m_dns_blocklist_interval;
std::list<epee::net_utils::network_address> m_priority_peers;
std::vector<epee::net_utils::network_address> m_exclusive_peers;
std::list<p2p_address> m_priority_peers;
std::vector<p2p_address> m_exclusive_peers;
std::atomic_flag m_fallback_seed_nodes_added;
std::vector<nodetool::peerlist_entry> m_command_line_peers;
uint64_t m_peer_livetime;
//keep connections to initiate some interactions
static boost::optional<p2p_connection_context> public_connect(network_zone&, epee::net_utils::network_address const&, epee::net_utils::ssl_support_t);
static boost::optional<p2p_connection_context> socks_connect(network_zone&, epee::net_utils::network_address const&, epee::net_utils::ssl_support_t);
static boost::optional<p2p_connection_context> public_connect(network_zone&, p2p_address const&);
static boost::optional<p2p_connection_context> socks_connect(network_zone&, p2p_address const&);
/* A `std::map` provides constant iterators and key/value pointers even with
@ -510,8 +557,6 @@ namespace nodetool
boost::uuids::uuid m_network_id;
cryptonote::network_type m_nettype;
epee::net_utils::ssl_support_t m_ssl_support;
bool m_enable_dns_seed_nodes;
bool m_enable_dns_blocklist;
@ -538,6 +583,8 @@ namespace nodetool
extern const command_line::arg_descriptor<bool> arg_p2p_hide_my_port;
extern const command_line::arg_descriptor<bool> arg_no_sync;
extern const command_line::arg_descriptor<bool> arg_enable_dns_blocklist;
extern const command_line::arg_descriptor<bool> arg_p2p_disable_encryption;
extern const command_line::arg_descriptor<bool> arg_p2p_persistent_cert;
extern const command_line::arg_descriptor<bool> arg_no_igd;
extern const command_line::arg_descriptor<std::string> arg_igd;

View File

@ -50,6 +50,7 @@
#include "common/util.h"
#include "common/dns_utils.h"
#include "common/pruning.h"
#include "hex.h"
#include "net/error.h"
#include "misc_log_ex.h"
#include "p2p_protocol_defs.h"
@ -80,6 +81,19 @@ static inline boost::asio::ip::address_v4 make_address_v4_from_v6(const boost::a
namespace nodetool
{
static string_address parse_address(std::string adr)
{
namespace net = epee::net_utils;
const auto pos = adr.find(',');
net::ssl_options_t e2e_mode{net::ssl_support_t::e_ssl_support_autodetect};
if (boost::string_ref{adr}.ends_with("no_encryption"))
e2e_mode = net::ssl_options_t{net::ssl_support_t::e_ssl_support_disabled};
else if (pos != std::string::npos)
e2e_mode = net::ssl_options_t{{epee::from_hex_locale::to_vector(boost::string_ref{adr}.substr(pos + 1))}, {}};
adr.erase(std::min(adr.size(), adr.find(',')));
return {std::move(adr) , std::move(e2e_mode)};
}
template<class t_payload_net_handler>
node_server<t_payload_net_handler>::~node_server()
{
@ -94,7 +108,7 @@ namespace nodetool
}
}
//-----------------------------------------------------------------------------------
inline bool append_net_address(std::vector<epee::net_utils::network_address> & seed_nodes, std::string const & addr, uint16_t default_port);
inline bool append_net_address(std::vector<p2p_address> & seed_nodes, string_address addr, uint16_t default_port);
//-----------------------------------------------------------------------------------
template<class t_payload_net_handler>
void node_server<t_payload_net_handler>::init_options(boost::program_options::options_description& desc)
@ -117,6 +131,8 @@ namespace nodetool
command_line::add_arg(desc, arg_p2p_hide_my_port);
command_line::add_arg(desc, arg_no_sync);
command_line::add_arg(desc, arg_enable_dns_blocklist);
command_line::add_arg(desc, arg_p2p_disable_encryption);
command_line::add_arg(desc, arg_p2p_persistent_cert);
command_line::add_arg(desc, arg_no_igd);
command_line::add_arg(desc, arg_igd);
command_line::add_arg(desc, arg_out_peers);
@ -407,7 +423,6 @@ namespace nodetool
{
bool testnet = command_line::get_arg(vm, cryptonote::arg_testnet_on);
bool stagenet = command_line::get_arg(vm, cryptonote::arg_stagenet_on);
const bool pad_txs = command_line::get_arg(vm, arg_pad_transactions);
m_nettype = testnet ? cryptonote::TESTNET : stagenet ? cryptonote::STAGENET : cryptonote::MAINNET;
network_zone& public_zone = m_network_zones[epee::net_utils::zone::public_];
@ -451,6 +466,8 @@ namespace nodetool
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_ignore_ipv4);
const bool pad_txs =
!command_line::get_arg(vm, arg_p2p_disable_encryption) || command_line::get_arg(vm, arg_pad_transactions);
public_zone.m_notifier = cryptonote::levin::notify{
public_zone.m_net_server.get_io_service(), public_zone.m_net_server.get_config_shared(), nullptr, epee::net_utils::zone::public_, pad_txs, m_payload_handler.get_core()
};
@ -463,11 +480,13 @@ namespace nodetool
nodetool::peerlist_entry pe = AUTO_VAL_INIT(pe);
pe.id = crypto::rand<uint64_t>();
const uint16_t default_port = cryptonote::get_config(m_nettype).P2P_DEFAULT_PORT;
expect<epee::net_utils::network_address> adr = net::get_network_address(pr_str, default_port);
string_address str_addr = parse_address(pr_str);
expect<epee::net_utils::network_address> adr = net::get_network_address(str_addr.address, default_port);
if (adr)
{
add_zone(adr->get_zone());
pe.adr = std::move(*adr);
p2p_address p2p_peer{std::move(*adr), std::move(str_addr.ssl)};
p2p_peer.update_peer(pe);
m_command_line_peers.push_back(std::move(pe));
continue;
}
@ -475,13 +494,13 @@ namespace nodetool
adr == net::error::unsupported_address, false, "Bad address (\"" << pr_str << "\"): " << adr.error().message()
);
std::vector<epee::net_utils::network_address> resolved_addrs;
bool r = append_net_address(resolved_addrs, pr_str, default_port);
std::vector<p2p_address> resolved_addrs;
bool r = append_net_address(resolved_addrs, str_addr, default_port);
CHECK_AND_ASSERT_MES(r, false, "Failed to parse or resolve address from string: " << pr_str);
for (const epee::net_utils::network_address& addr : resolved_addrs)
for (const p2p_address& peer : resolved_addrs)
{
pe.id = crypto::rand<uint64_t>();
pe.adr = addr;
peer.update_peer(pe);
m_command_line_peers.push_back(pe);
}
}
@ -605,7 +624,7 @@ namespace nodetool
}
zone.m_notifier = cryptonote::levin::notify{
zone.m_net_server.get_io_service(), zone.m_net_server.get_config_shared(), std::move(this_noise), proxy.zone, pad_txs, m_payload_handler.get_core()
zone.m_net_server.get_io_service(), zone.m_net_server.get_config_shared(), std::move(this_noise), proxy.zone, true /* pad txs */, m_payload_handler.get_core()
};
}
@ -654,8 +673,8 @@ namespace nodetool
}
//-----------------------------------------------------------------------------------
inline bool append_net_address(
std::vector<epee::net_utils::network_address> & seed_nodes
, std::string const & addr
std::vector<p2p_address> & seed_nodes
, string_address peer
, uint16_t default_port
)
{
@ -664,7 +683,7 @@ namespace nodetool
// Split addr string into host string and port string
std::string host;
std::string port = std::to_string(default_port);
net::get_network_address_host_and_port(addr, host, port);
net::get_network_address_host_and_port(peer.address, host, port);
MINFO("Resolving node address: host=" << host << ", port=" << port);
io_service io_srv;
@ -681,13 +700,13 @@ namespace nodetool
if (endpoint.address().is_v4())
{
epee::net_utils::network_address na{epee::net_utils::ipv4_network_address{boost::asio::detail::socket_ops::host_to_network_long(endpoint.address().to_v4().to_ulong()), endpoint.port()}};
seed_nodes.push_back(na);
seed_nodes.push_back(p2p_address{na, peer.ssl});
MINFO("Added node: " << na.str());
}
else
{
epee::net_utils::network_address na{epee::net_utils::ipv6_network_address{endpoint.address().to_v6(), endpoint.port()}};
seed_nodes.push_back(na);
seed_nodes.push_back(p2p_address{na, peer.ssl});
MINFO("Added node: " << na.str());
}
}
@ -696,43 +715,44 @@ namespace nodetool
//-----------------------------------------------------------------------------------
template<class t_payload_net_handler>
std::set<std::string> node_server<t_payload_net_handler>::get_ip_seed_nodes() const
std::set<string_address> node_server<t_payload_net_handler>::get_ip_seed_nodes() const
{
std::set<std::string> full_addrs;
const auto autodetect = epee::net_utils::ssl_support_t::e_ssl_support_autodetect;
std::set<string_address> full_addrs;
if (m_nettype == cryptonote::TESTNET)
{
full_addrs.insert("176.9.0.187:28080");
full_addrs.insert("51.79.173.165:28080");
full_addrs.insert("192.99.8.110:28080");
full_addrs.insert("37.187.74.171:28080");
full_addrs.insert("77.172.183.193:28080");
full_addrs.insert(string_address{"176.9.0.187:28080", autodetect});
full_addrs.insert(string_address{"51.79.173.165:28080", autodetect});
full_addrs.insert(string_address{"192.99.8.110:28080", autodetect});
full_addrs.insert(string_address{"37.187.74.171:28080", autodetect});
full_addrs.insert(string_address{"77.172.183.193:28080", autodetect});
}
else if (m_nettype == cryptonote::STAGENET)
{
full_addrs.insert("176.9.0.187:38080");
full_addrs.insert("51.79.173.165:38080");
full_addrs.insert("192.99.8.110:38080");
full_addrs.insert("37.187.74.171:38080");
full_addrs.insert("77.172.183.193:38080");
full_addrs.insert(string_address{"176.9.0.187:38080", autodetect});
full_addrs.insert(string_address{"51.79.173.165:38080", autodetect});
full_addrs.insert(string_address{"192.99.8.110:38080", autodetect});
full_addrs.insert(string_address{"37.187.74.171:38080", autodetect});
full_addrs.insert(string_address{"77.172.183.193:38080", autodetect});
}
else if (m_nettype == cryptonote::FAKECHAIN)
{
}
else
{
full_addrs.insert("176.9.0.187:18080");
full_addrs.insert("88.198.163.90:18080");
full_addrs.insert("66.85.74.134:18080");
full_addrs.insert("51.79.173.165:18080");
full_addrs.insert("192.99.8.110:18080");
full_addrs.insert("37.187.74.171:18080");
full_addrs.insert("77.172.183.193:18080");
full_addrs.insert(string_address{"176.9.0.187:18080", autodetect});
full_addrs.insert(string_address{"88.198.163.90:18080", autodetect});
full_addrs.insert(string_address{"66.85.74.134:18080", autodetect});
full_addrs.insert(string_address{"51.79.173.165:18080", autodetect});
full_addrs.insert(string_address{"192.99.8.110:18080", autodetect});
full_addrs.insert(string_address{"37.187.74.171:18080", autodetect});
full_addrs.insert(string_address{"77.172.183.193:18080", autodetect});
}
return full_addrs;
}
//-----------------------------------------------------------------------------------
template<class t_payload_net_handler>
std::set<std::string> node_server<t_payload_net_handler>::get_dns_seed_nodes()
std::set<string_address> node_server<t_payload_net_handler>::get_dns_seed_nodes()
{
if (!m_exclusive_peers.empty() || m_offline)
{
@ -753,7 +773,7 @@ namespace nodetool
return get_ip_seed_nodes();
}
std::set<std::string> full_addrs;
std::set<string_address> full_addrs;
// for each hostname in the seed nodes list, attempt to DNS resolve and
// add the result addresses as seed nodes
@ -825,7 +845,7 @@ namespace nodetool
if (result.size())
{
for (const auto& addr_string : result)
full_addrs.insert(addr_string + ":" + std::to_string(cryptonote::get_config(m_nettype).P2P_DEFAULT_PORT));
full_addrs.insert(string_address{addr_string + ":" + std::to_string(cryptonote::get_config(m_nettype).P2P_DEFAULT_PORT), epee::net_utils::ssl_support_t::e_ssl_support_disabled});
}
++i;
}
@ -847,8 +867,10 @@ namespace nodetool
}
//-----------------------------------------------------------------------------------
template<class t_payload_net_handler>
std::set<std::string> node_server<t_payload_net_handler>::get_seed_nodes(epee::net_utils::zone zone)
std::set<string_address> node_server<t_payload_net_handler>::get_seed_nodes(epee::net_utils::zone zone)
{
// `.onion` and `.i2p` are already end-to-end encrypted
const auto no_encryption = epee::net_utils::ssl_support_t::e_ssl_support_disabled;
switch (zone)
{
case epee::net_utils::zone::public_:
@ -856,23 +878,25 @@ namespace nodetool
case epee::net_utils::zone::tor:
if (m_nettype == cryptonote::MAINNET)
{
// tor hidden services are already e2e ssl encrypted
return {
"zbjkbsxc5munw3qusl7j2hpcmikhqocdf4pqhnhtpzw5nt5jrmofptid.onion:18083",
"qz43zul2x56jexzoqgkx2trzwcfnr6l3hbtfcfx54g4r3eahy3bssjyd.onion:18083",
"plowsof3t5hogddwabaeiyrno25efmzfxyro2vligremt7sxpsclfaid.onion:18083",
"plowsoffjexmxalw73tkjmf422gq6575fc7vicuu4javzn2ynnte6tyd.onion:18083",
"plowsofe6cleftfmk2raiw5h2x66atrik3nja4bfd3zrfa2hdlgworad.onion:18083",
"aclc4e2jhhtr44guufbnwk5bzwhaecinax4yip4wr4tjn27sjsfg6zqd.onion:18083",
string_address{"zbjkbsxc5munw3qusl7j2hpcmikhqocdf4pqhnhtpzw5nt5jrmofptid.onion:18083", no_encryption},
string_address{"qz43zul2x56jexzoqgkx2trzwcfnr6l3hbtfcfx54g4r3eahy3bssjyd.onion:18083", no_encryption},
string_address{"plowsof3t5hogddwabaeiyrno25efmzfxyro2vligremt7sxpsclfaid.onion:18083", no_encryption},
string_address{"plowsoffjexmxalw73tkjmf422gq6575fc7vicuu4javzn2ynnte6tyd.onion:18083", no_encryption},
string_address{"plowsofe6cleftfmk2raiw5h2x66atrik3nja4bfd3zrfa2hdlgworad.onion:18083", no_encryption},
string_address{"aclc4e2jhhtr44guufbnwk5bzwhaecinax4yip4wr4tjn27sjsfg6zqd.onion:18083", no_encryption},
};
}
return {};
case epee::net_utils::zone::i2p:
if (m_nettype == cryptonote::MAINNET)
{
// i2p services are already e2e aes noise encrypted
return {
"uqj3aphckqtjsitz7kxx5flqpwjlq5ppr3chazfued7xucv3nheq.b32.i2p",
"vdmnehdjkpkg57nthgnjfuaqgku673r5bpbqg56ix6fyqoywgqrq.b32.i2p",
"ugnlcdciyhghh2zert7c3kl4biwkirc43ke33jiy5slnd3mv2trq.b32.i2p",
string_address{"uqj3aphckqtjsitz7kxx5flqpwjlq5ppr3chazfued7xucv3nheq.b32.i2p", no_encryption},
string_address{"vdmnehdjkpkg57nthgnjfuaqgku673r5bpbqg56ix6fyqoywgqrq.b32.i2p", no_encryption},
string_address{"ugnlcdciyhghh2zert7c3kl4biwkirc43ke33jiy5slnd3mv2trq.b32.i2p", no_encryption}
};
}
return {};
@ -956,7 +980,6 @@ namespace nodetool
return res;
//try to bind
m_ssl_support = epee::net_utils::ssl_support_t::e_ssl_support_disabled;
for (auto& zone : m_network_zones)
{
zone.second.m_net_server.get_config_object().set_handler(this);
@ -974,8 +997,39 @@ namespace nodetool
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);
namespace net = epee::net_utils;
bool store_cert = false;
bool share_fingerprint = false;
net::ssl_options_t p2p_ssl{net::ssl_support_t::e_ssl_support_disabled};
const auto key_files = m_config_folder + "/p2p_public";
if (zone.first == epee::net_utils::zone::public_ && !command_line::get_arg(vm, arg_p2p_disable_encryption))
{
p2p_ssl = net::ssl_options_t{net::ssl_support_t::e_ssl_support_autodetect};
if (command_line::get_arg(vm, arg_p2p_persistent_cert))
{
share_fingerprint = true;
p2p_ssl = net::ssl_options_t{net::ssl_support_t::e_ssl_support_enabled};
MINFO("Using persistent SSL certificate for p2p");
if (boost::filesystem::exists(key_files + ".key") && boost::filesystem::exists(key_files + ".crt"))
{
p2p_ssl.auth.private_key_path = key_files + ".key";
p2p_ssl.auth.certificate_path = key_files + ".crt";
}
else
store_cert = true;
}
}
else
MINFO("Generating new temporary SSL certificate for p2p");
zone.second.m_ssl_support = p2p_ssl.support;
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, std::move(p2p_ssl));
CHECK_AND_ASSERT_MES(res, false, "Failed to bind server");
if (store_cert)
net::store_ssl_keys(zone.second.m_net_server.get_ssl_context(), key_files);
if (share_fingerprint)
zone.second.m_ssl_finger = net::get_ssl_fingerprint(zone.second.m_net_server.get_ssl_context());
}
}
@ -1138,7 +1192,6 @@ namespace nodetool
bool node_server<t_payload_net_handler>::do_handshake_with_peer(peerid_type& pi, p2p_connection_context& context_, bool just_take_peerlist)
{
network_zone& zone = m_network_zones.at(context_.m_remote_address.get_zone());
typename COMMAND_HANDSHAKE::request arg;
typename COMMAND_HANDSHAKE::response rsp;
get_local_node_data(arg.node_data, zone);
@ -1187,6 +1240,8 @@ namespace nodetool
context.m_rpc_port = rsp.node_data.rpc_port;
context.m_rpc_credits_per_hash = rsp.node_data.rpc_credits_per_hash;
context.support_flags = rsp.node_data.support_flags;
context.encryption_mode = rsp.node_data.encryption_mode;
context.cert_finger = std::move(rsp.node_data.cert_finger);
const auto azone = context.m_remote_address.get_zone();
network_zone& zone = m_network_zones.at(azone);
zone.m_peerlist.set_peer_just_seen(rsp.node_data.peer_id, context.m_remote_address, context.m_pruning_seed, context.m_rpc_port, context.m_rpc_credits_per_hash);
@ -1363,13 +1418,13 @@ namespace nodetool
} while(0)
template<class t_payload_net_handler>
bool node_server<t_payload_net_handler>::try_to_connect_and_handshake_with_new_peer(const epee::net_utils::network_address& na, bool just_take_peerlist, uint64_t last_seen_stamp, PeerType peer_type, uint64_t first_seen_stamp)
bool node_server<t_payload_net_handler>::try_to_connect_and_handshake_with_new_peer(const p2p_address& peer, bool just_take_peerlist, uint64_t last_seen_stamp, PeerType peer_type, uint64_t first_seen_stamp)
{
network_zone& zone = m_network_zones.at(na.get_zone());
network_zone& zone = m_network_zones.at(peer.na.get_zone());
if (zone.m_connect == nullptr) // outgoing connections in zone not possible
return false;
if (zone.m_our_address == na)
if (zone.m_our_address == peer.na)
return false;
if (zone.m_current_number_of_out_peers == zone.m_config.m_net_config.max_out_connection_count) // out peers limit
@ -1384,17 +1439,17 @@ namespace nodetool
}
MDEBUG("Connecting to " << na.str() << "(peer_type=" << peer_type << ", last_seen: "
MDEBUG("Connecting to " << peer.na.str() << "(peer_type=" << peer_type << ", last_seen: "
<< (last_seen_stamp ? epee::misc_utils::get_time_interval_string(time(NULL) - last_seen_stamp):"never")
<< ")...");
auto con = zone.m_connect(zone, na, m_ssl_support);
auto con = zone.m_connect(zone, peer);
if(!con)
{
bool is_priority = is_priority_node(na);
LOG_PRINT_CC_PRIORITY_NODE(is_priority, bool(con), "Connect failed to " << na.str()
bool is_priority = is_priority_node(peer.na);
LOG_PRINT_CC_PRIORITY_NODE(is_priority, bool(con), "Connect failed to " << peer.na.str()
/*<< ", try " << try_count*/);
record_addr_failed(na);
record_addr_failed(peer.na);
return false;
}
@ -1404,11 +1459,11 @@ namespace nodetool
if(!res)
{
bool is_priority = is_priority_node(na);
bool is_priority = is_priority_node(peer.na);
LOG_PRINT_CC_PRIORITY_NODE(is_priority, *con, "Failed to HANDSHAKE with peer "
<< na.str()
<< peer.na.str()
/*<< ", try " << try_count*/);
record_addr_failed(na);
record_addr_failed(peer.na);
return false;
}
@ -1420,7 +1475,7 @@ namespace nodetool
}
peerlist_entry pe_local = AUTO_VAL_INIT(pe_local);
pe_local.adr = na;
pe_local.adr = peer.na;
pe_local.id = pi;
time_t last_seen;
time(&last_seen);
@ -1428,13 +1483,17 @@ namespace nodetool
pe_local.pruning_seed = con->m_pruning_seed;
pe_local.rpc_port = con->m_rpc_port;
pe_local.rpc_credits_per_hash = con->m_rpc_credits_per_hash;
pe_local.encryption_mode = con->encryption_mode;
pe_local.cert_finger = con->cert_finger;
zone.m_peerlist.append_with_peer_white(pe_local);
//update last seen and push it to peerlist manager
anchor_peerlist_entry ape = AUTO_VAL_INIT(ape);
ape.adr = na;
ape.adr = peer.na;
ape.id = pi;
ape.first_seen = first_seen_stamp ? first_seen_stamp : time(nullptr);
ape.encryption_mode = con->encryption_mode;
ape.cert_finger = con->cert_finger;
zone.m_peerlist.append_with_peer_anchor(ape);
zone.m_notifier.on_handshake_complete(con->m_connection_id, con->m_is_income);
@ -1445,22 +1504,22 @@ namespace nodetool
}
template<class t_payload_net_handler>
bool node_server<t_payload_net_handler>::check_connection_and_handshake_with_peer(const epee::net_utils::network_address& na, uint64_t last_seen_stamp)
bool node_server<t_payload_net_handler>::check_connection_and_handshake_with_peer(const p2p_address& peer, uint64_t last_seen_stamp)
{
network_zone& zone = m_network_zones.at(na.get_zone());
network_zone& zone = m_network_zones.at(peer.na.get_zone());
if (zone.m_connect == nullptr)
return false;
LOG_PRINT_L1("Connecting to " << na.str() << "(last_seen: "
LOG_PRINT_L1("Connecting to " << peer.na.str() << "(last_seen: "
<< (last_seen_stamp ? epee::misc_utils::get_time_interval_string(time(NULL) - last_seen_stamp):"never")
<< ")...");
auto con = zone.m_connect(zone, na, m_ssl_support);
auto con = zone.m_connect(zone, peer);
if (!con) {
bool is_priority = is_priority_node(na);
bool is_priority = is_priority_node(peer.na);
LOG_PRINT_CC_PRIORITY_NODE(is_priority, p2p_connection_context{}, "Connect failed to " << na.str());
record_addr_failed(na);
LOG_PRINT_CC_PRIORITY_NODE(is_priority, p2p_connection_context{}, "Connect failed to " << peer.na.str());
record_addr_failed(peer.na);
return false;
}
@ -1469,10 +1528,10 @@ namespace nodetool
peerid_type pi = AUTO_VAL_INIT(pi);
const bool res = do_handshake_with_peer(pi, *con, true);
if (!res) {
bool is_priority = is_priority_node(na);
bool is_priority = is_priority_node(peer.na);
LOG_PRINT_CC_PRIORITY_NODE(is_priority, *con, "Failed to HANDSHAKE with peer " << na.str());
record_addr_failed(na);
LOG_PRINT_CC_PRIORITY_NODE(is_priority, *con, "Failed to HANDSHAKE with peer " << peer.na.str());
record_addr_failed(peer.na);
return false;
}
@ -1530,7 +1589,7 @@ namespace nodetool
<< "[peer_type=" << anchor
<< "] first_seen: " << epee::misc_utils::get_time_interval_string(time(NULL) - pe.first_seen));
if(!try_to_connect_and_handshake_with_new_peer(pe.adr, false, 0, anchor, pe.first_seen)) {
if(!try_to_connect_and_handshake_with_new_peer({pe.adr, get_p2p_encryption(pe)}, false, 0, anchor, pe.first_seen)) {
_note("Handshake failed");
continue;
}
@ -1719,7 +1778,7 @@ namespace nodetool
<< "[peer_list=" << (use_white_list ? white : gray)
<< "] last_seen: " << (pe.last_seen ? epee::misc_utils::get_time_interval_string(time(NULL) - pe.last_seen) : "never"));
if(!try_to_connect_and_handshake_with_new_peer(pe.adr, false, pe.last_seen, use_white_list ? white : gray)) {
if(!try_to_connect_and_handshake_with_new_peer({pe.adr, get_p2p_encryption(pe)}, false, pe.last_seen, use_white_list ? white : gray)) {
_note("Handshake failed");
continue;
}
@ -1743,8 +1802,8 @@ namespace nodetool
for (const auto& full_addr : get_seed_nodes(zone))
{
// seeds should have hostname converted to IP already
MDEBUG("Seed node: " << full_addr);
server.m_seed_nodes.push_back(MONERO_UNWRAP(net::get_network_address(full_addr, default_port)));
MDEBUG("Seed node: " << full_addr.address);
server.m_seed_nodes.push_back(p2p_address{MONERO_UNWRAP(net::get_network_address(full_addr.address, default_port)), full_addr.ssl});
}
MDEBUG("Number of seed nodes: " << server.m_seed_nodes.size());
}
@ -1761,7 +1820,7 @@ namespace nodetool
return false;
peerlist_entry pe_seed{};
pe_seed.adr = server.m_seed_nodes[current_index];
pe_seed.adr = server.m_seed_nodes[current_index].na;
if (is_peer_used(pe_seed))
is_connected_to_at_least_one_seed_node = true;
else if (try_to_connect_and_handshake_with_new_peer(server.m_seed_nodes[current_index], true))
@ -1778,7 +1837,7 @@ namespace nodetool
for (const auto &peer: get_ip_seed_nodes())
{
MDEBUG("Fallback seed node: " << peer);
MDEBUG("Fallback seed node: " << peer.address);
append_net_address(server.m_seed_nodes, peer, cryptonote::get_config(m_nettype).P2P_DEFAULT_PORT);
}
}
@ -2206,6 +2265,8 @@ namespace nodetool
node_data.rpc_credits_per_hash = zone.m_can_pingback ? m_rpc_credits_per_hash : 0;
node_data.network_id = m_network_id;
node_data.support_flags = zone.m_config.m_support_flags;
node_data.encryption_mode = get_encryption_mode(zone.m_ssl_support);
node_data.cert_finger = zone.m_ssl_finger;
return true;
}
//-----------------------------------------------------------------------------------
@ -2225,7 +2286,14 @@ namespace nodetool
template<class t_payload_net_handler>
bool node_server<t_payload_net_handler>::relay_notify_to_list(int command, epee::levin::message_writer data_buff, std::vector<std::pair<epee::net_utils::zone, boost::uuids::uuid>> connections)
{
epee::byte_slice message = data_buff.finalize_notify(command);
/* This is typically used for relaying blocks, and conveniently uses the
same ref-counted buffer for every connection. Unfortunately, this means
every connection is sent a message of identical size, which should be
identifiable by a ISP/level spy. The alternatives are less efficient (a
uniquely padded message foreach link). Defending against this type of
analysis is likely always complicated, as each message type will have
unique */
epee::byte_slice message = data_buff.finalize_notify(command, false);
std::sort(connections.begin(), connections.end());
auto zone = m_network_zones.begin();
for(const auto& c_id: connections)
@ -2317,7 +2385,7 @@ namespace nodetool
return false;
network_zone& zone = m_network_zones.at(context.m_remote_address.get_zone());
int res = zone.m_net_server.get_config_object().send(message.finalize_notify(command), context.m_connection_id);
int res = zone.m_net_server.get_config_object().send(message.finalize_notify(command, context.should_pad()), context.m_connection_id);
return res > 0;
}
//-----------------------------------------------------------------------------------
@ -2371,6 +2439,7 @@ namespace nodetool
{
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,
@ -2421,7 +2490,7 @@ namespace nodetool
return false;
}
return true;
}, "0.0.0.0", m_ssl_support);
}, "0.0.0.0", get_p2p_encryption(node_data));
if(!r)
{
LOG_WARNING_CC(context, "Failed to call connect_async, network error.");
@ -2498,7 +2567,7 @@ namespace nodetool
real peer with that identity banned/blacklisted. */
if(outgoing_to_same_zone)
rsp.local_peerlist_new.push_back(peerlist_entry{zone.m_our_address, zone.m_config.m_peer_id, std::time(nullptr)});
rsp.local_peerlist_new.push_back(peerlist_entry{zone.m_our_address, {}, zone.m_config.m_peer_id, std::time(nullptr)});
LOG_DEBUG_CC(context, "COMMAND_TIMED_SYNC");
return 1;
@ -2578,7 +2647,7 @@ namespace nodetool
peerid_type peer_id_l = arg.node_data.peer_id;
uint32_t port_l = arg.node_data.my_port;
//try ping to be sure that we can add this peer to peer_list
try_ping(arg.node_data, context, [peer_id_l, port_l, context, this]()
try_ping(arg.node_data, context, [arg, 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() || 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");
@ -2600,6 +2669,8 @@ namespace nodetool
pe.pruning_seed = context.m_pruning_seed;
pe.rpc_port = context.m_rpc_port;
pe.rpc_credits_per_hash = context.m_rpc_credits_per_hash;
pe.encryption_mode = arg.node_data.encryption_mode;
pe.cert_finger = arg.node_data.cert_finger;
this->m_network_zones.at(context.m_remote_address.get_zone()).m_peerlist.append_with_peer_white(pe);
LOG_DEBUG_CC(context, "PING SUCCESS " << context.m_remote_address.host_str() << ":" << port_l);
});
@ -2696,22 +2767,22 @@ namespace nodetool
template<class t_payload_net_handler>
bool node_server<t_payload_net_handler>::is_priority_node(const epee::net_utils::network_address& na)
{
return (std::find(m_priority_peers.begin(), m_priority_peers.end(), na) != m_priority_peers.end()) || (std::find(m_exclusive_peers.begin(), m_exclusive_peers.end(), na) != m_exclusive_peers.end());
return (std::find(m_priority_peers.begin(), m_priority_peers.end(), p2p_address{na}) != m_priority_peers.end()) || (std::find(m_exclusive_peers.begin(), m_exclusive_peers.end(), p2p_address{na}) != m_exclusive_peers.end());
}
template<class t_payload_net_handler> template <class Container>
bool node_server<t_payload_net_handler>::connect_to_peerlist(const Container& peers)
{
const network_zone& public_zone = m_network_zones.at(epee::net_utils::zone::public_);
for(const epee::net_utils::network_address& na: peers)
for(const p2p_address& peer: peers)
{
if(public_zone.m_net_server.is_stop_signal_sent())
return false;
if(is_addr_connected(na))
if(is_addr_connected(peer.na))
continue;
try_to_connect_and_handshake_with_new_peer(na);
try_to_connect_and_handshake_with_new_peer(peer);
}
return true;
@ -2725,19 +2796,20 @@ namespace nodetool
for(const std::string& pr_str: perrs)
{
const uint16_t default_port = cryptonote::get_config(m_nettype).P2P_DEFAULT_PORT;
expect<epee::net_utils::network_address> adr = net::get_network_address(pr_str, default_port);
auto peer = parse_address(pr_str);
expect<epee::net_utils::network_address> adr = net::get_network_address(peer.address, default_port);
if (adr)
{
add_zone(adr->get_zone());
container.push_back(std::move(*adr));
container.push_back(p2p_address{std::move(*adr), std::move(peer.ssl)});
continue;
}
std::vector<epee::net_utils::network_address> resolved_addrs;
bool r = append_net_address(resolved_addrs, pr_str, default_port);
std::vector<p2p_address> resolved_addrs;
bool r = append_net_address(resolved_addrs, peer, default_port);
CHECK_AND_ASSERT_MES(r, false, "Failed to parse or resolve address from string: " << pr_str);
for (const epee::net_utils::network_address& addr : resolved_addrs)
for (p2p_address& addr : resolved_addrs)
{
container.push_back(addr);
container.push_back(std::move(addr));
}
}
@ -2916,8 +2988,8 @@ namespace nodetool
peerlist_entry pe{};
if (!zone.second.m_peerlist.get_random_gray_peer(pe))
continue;
if (!check_connection_and_handshake_with_peer(pe.adr, pe.last_seen))
if (!check_connection_and_handshake_with_peer({pe.adr, get_p2p_encryption(pe)}, pe.last_seen))
{
zone.second.m_peerlist.remove_from_peer_gray(pe);
LOG_PRINT_L2("PEER EVICTED FROM GRAY PEER LIST: address: " << pe.adr.host_str() << " Peer ID: " << peerid_to_string(pe.id));
@ -3102,13 +3174,13 @@ namespace nodetool
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)
node_server<t_payload_net_handler>::socks_connect(network_zone& zone, const p2p_address& peer)
{
auto result = socks_connect_internal(zone.m_net_server.get_stop_signal(), zone.m_net_server.get_io_service(), zone.m_proxy_address, remote);
auto result = socks_connect_internal(zone.m_net_server.get_stop_signal(), zone.m_net_server.get_io_service(), zone.m_proxy_address, peer.na);
if (result) // if no error
{
p2p_connection_context context{};
if (zone.m_net_server.add_connection(context, std::move(*result), remote, ssl_support))
if (zone.m_net_server.add_connection(context, std::move(*result), peer.na, epee::net_utils::ssl_support_t::e_ssl_support_disabled))
return {std::move(context)};
}
return boost::none;
@ -3116,10 +3188,10 @@ namespace nodetool
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>::public_connect(network_zone& zone, epee::net_utils::network_address const& na, epee::net_utils::ssl_support_t ssl_support)
node_server<t_payload_net_handler>::public_connect(network_zone& zone, const p2p_address& peer)
{
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();
bool is_ipv4 = peer.na.get_type_id() == epee::net_utils::ipv4_network_address::get_type_id();
bool is_ipv6 = peer.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");
@ -3128,13 +3200,13 @@ namespace nodetool
if (is_ipv4)
{
const epee::net_utils::ipv4_network_address &ipv4 = na.as<const epee::net_utils::ipv4_network_address>();
const epee::net_utils::ipv4_network_address &ipv4 = peer.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>();
const epee::net_utils::ipv6_network_address &ipv6 = peer.na.as<const epee::net_utils::ipv6_network_address>();
address = ipv6.ip().to_string();
port = epee::string_tools::num_to_string_fast(ipv6.port());
}
@ -3144,10 +3216,14 @@ namespace nodetool
return boost::none;
}
const auto ssl =
zone.m_ssl_support == epee::net_utils::ssl_support_t::e_ssl_support_disabled ?
zone.m_ssl_support : peer.ssl;
typename net_server::t_connection_context con{};
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);
con, "0.0.0.0", ssl);
if (res)
return {std::move(con)};

View File

@ -396,20 +396,30 @@ namespace nodetool
//update gray list
auto by_addr_it_gr = m_peers_gray.get<by_addr>().find(ple.adr);
peerlist_entry new_ple = ple;
if(by_addr_it_gr == m_peers_gray.get<by_addr>().end())
{
//put new record into white list
m_peers_gray.insert(ple);
// Do not trust encryption information from 3rd parties
new_ple.encryption_mode = emode_ssl_autodetect;
new_ple.cert_finger.clear();
m_peers_gray.insert(new_ple);
trim_gray_peerlist();
}else
{
//update record in gray list
peerlist_entry new_ple = ple;
if (by_addr_it_gr->pruning_seed && ple.pruning_seed == 0) // guard against older nodes not passing pruning info around
new_ple.pruning_seed = by_addr_it_gr->pruning_seed;
if (by_addr_it_gr->rpc_port && ple.rpc_port == 0) // guard against older nodes not passing RPC port around
new_ple.rpc_port = by_addr_it_gr->rpc_port;
new_ple.last_seen = by_addr_it_gr->last_seen; // do not overwrite the last seen timestamp, incoming peer list are untrusted
// use existing values for encryption on replace
new_ple.encryption_mode = by_addr_it_gr->encryption_mode;
new_ple.cert_finger = by_addr_it_gr->cert_finger;
m_peers_gray.replace(by_addr_it_gr, new_ple);
}
return true;
@ -420,7 +430,6 @@ namespace nodetool
bool peerlist_manager::append_with_peer_anchor(const anchor_peerlist_entry& ple)
{
TRY_ENTRY();
CRITICAL_REGION_LOCAL(m_peerlist_lock);
auto by_addr_it_anchor = m_peers_anchor.get<by_addr>().find(ple.adr);

View File

@ -38,7 +38,8 @@
#include "net/i2p_address.h"
#include "p2p/p2p_protocol_defs.h"
BOOST_CLASS_VERSION(nodetool::peerlist_entry, 3)
BOOST_CLASS_VERSION(nodetool::peerlist_entry, 4)
BOOST_CLASS_VERSION(nodetool::anchor_peerlist_entry, 1)
namespace boost
{
@ -238,6 +239,18 @@ namespace boost
return;
}
a & pl.rpc_credits_per_hash;
if (ver < 4)
{
if (!typename Archive::is_saving())
{
pl.encryption_mode = nodetool::emode_ssl_autodetect;
pl.cert_finger.clear();
}
return;
}
a & pl.encryption_mode;
a & pl.cert_finger;
}
template <class Archive, class ver_type>
@ -246,6 +259,17 @@ namespace boost
a & pl.adr;
a & pl.id;
a & pl.first_seen;
if (ver < 1)
{
if (!typename Archive::is_saving())
{
pl.encryption_mode = nodetool::emode_ssl_autodetect;
pl.cert_finger.clear();
}
return;
}
a & pl.encryption_mode;
a & pl.cert_finger;
}
}
}

View File

@ -33,10 +33,12 @@
#include <iomanip>
#include <boost/uuid/uuid.hpp>
#include <boost/serialization/version.hpp>
#include <boost/variant/variant.hpp>
#include "serialization/keyvalue_serialization.h"
#include "net/net_utils_base.h"
#include "net/tor_address.h" // needed for serialization
#include "net/i2p_address.h" // needed for serialization
#include "net/net_ssl.h"
#include "misc_language.h"
#include "string_tools.h"
#include "time_helper.h"
@ -48,6 +50,11 @@ namespace nodetool
typedef boost::uuids::uuid uuid;
typedef uint64_t peerid_type;
// The current modes/versions for p2p encryption
constexpr uint8_t emode_ssl_autodetect = 0;
constexpr uint8_t emode_disabled = 1;
constexpr uint8_t emode_ssl_enabled = 2;
static inline std::string peerid_to_string(peerid_type peer_id)
{
std::ostringstream s;
@ -55,6 +62,48 @@ namespace nodetool
return epee::string_tools::pad_string(s.str(), 16, '0', true);
}
inline uint8_t get_encryption_mode(const epee::net_utils::ssl_support_t support)
{
switch (support)
{
case epee::net_utils::ssl_support_t::e_ssl_support_enabled:
return emode_ssl_enabled;
case epee::net_utils::ssl_support_t::e_ssl_support_disabled:
return emode_disabled;
default:
case epee::net_utils::ssl_support_t::e_ssl_support_autodetect:
break;
}
return emode_ssl_autodetect;
}
template<typename T>
inline epee::net_utils::ssl_options_t get_p2p_encryption(const T& peer)
{
namespace net = epee::net_utils;
switch (peer.encryption_mode)
{
default:
case emode_ssl_autodetect:
return net::ssl_support_t::e_ssl_support_autodetect;
case emode_disabled:
return net::ssl_support_t::e_ssl_support_disabled;
case emode_ssl_enabled:
break;
}
if (peer.cert_finger.empty())
{
// SSL is required, but no verification
net::ssl_options_t out = net::ssl_support_t::e_ssl_support_enabled;
out.verification = net::ssl_verification_t::none;
return out;
}
// Fingerprint specified, require SSL
std::vector<std::vector<std::uint8_t>> fingers{net::convert_fingerprint(peer.cert_finger)};
return {std::move(fingers), {}};
}
#pragma pack (push, 1)
struct network_address_old
@ -72,11 +121,13 @@ namespace nodetool
struct peerlist_entry_base
{
AddressType adr;
std::string cert_finger;
peerid_type id;
int64_t last_seen;
uint32_t pruning_seed;
uint16_t rpc_port;
uint32_t rpc_credits_per_hash;
uint8_t encryption_mode;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(adr)
@ -85,16 +136,9 @@ namespace nodetool
KV_SERIALIZE_OPT(pruning_seed, (uint32_t)0)
KV_SERIALIZE_OPT(rpc_port, (uint16_t)0)
KV_SERIALIZE_OPT(rpc_credits_per_hash, (uint32_t)0)
KV_SERIALIZE_OPT(encryption_mode, emode_ssl_autodetect)
KV_SERIALIZE_OPT(cert_finger, std::string{})
END_KV_SERIALIZE_MAP()
BEGIN_SERIALIZE()
FIELD(adr)
FIELD(id)
VARINT_FIELD(last_seen)
VARINT_FIELD(pruning_seed)
VARINT_FIELD(rpc_port)
VARINT_FIELD(rpc_credits_per_hash)
END_SERIALIZE()
};
typedef peerlist_entry_base<epee::net_utils::network_address> peerlist_entry;
@ -102,44 +146,21 @@ namespace nodetool
struct anchor_peerlist_entry_base
{
AddressType adr;
std::string cert_finger;
peerid_type id;
int64_t first_seen;
uint8_t encryption_mode;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(adr)
KV_SERIALIZE(id)
KV_SERIALIZE(first_seen)
KV_SERIALIZE_OPT(encryption_mode, emode_ssl_autodetect)
KV_SERIALIZE_OPT(cert_finger, std::string{})
END_KV_SERIALIZE_MAP()
BEGIN_SERIALIZE()
FIELD(adr)
FIELD(id)
VARINT_FIELD(first_seen)
END_SERIALIZE()
};
typedef anchor_peerlist_entry_base<epee::net_utils::network_address> anchor_peerlist_entry;
template<typename AddressType>
struct connection_entry_base
{
AddressType adr;
peerid_type id;
bool is_income;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(adr)
KV_SERIALIZE(id)
KV_SERIALIZE(is_income)
END_KV_SERIALIZE_MAP()
BEGIN_SERIALIZE()
FIELD(adr)
FIELD(id)
FIELD(is_income)
END_SERIALIZE()
};
typedef connection_entry_base<epee::net_utils::network_address> connection_entry;
#pragma pack(pop)
inline
@ -184,12 +205,14 @@ namespace nodetool
struct basic_node_data
{
uuid network_id;
std::string cert_finger;
uuid network_id;
uint32_t my_port;
uint16_t rpc_port;
uint32_t rpc_credits_per_hash;
peerid_type peer_id;
uint32_t support_flags;
uint8_t encryption_mode;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE_VAL_POD_AS_BLOB(network_id)
@ -198,6 +221,8 @@ namespace nodetool
KV_SERIALIZE_OPT(rpc_port, (uint16_t)(0))
KV_SERIALIZE_OPT(rpc_credits_per_hash, (uint32_t)0)
KV_SERIALIZE_OPT(support_flags, (uint32_t)0)
KV_SERIALIZE_OPT(encryption_mode, emode_ssl_autodetect)
KV_SERIALIZE_OPT(cert_finger, std::string{})
END_KV_SERIALIZE_MAP()
};

View File

@ -56,9 +56,9 @@ monerod_base = [builddir + "/bin/monerod", "--regtest", "--fixed-difficulty", st
monerod_extra = [
["--offline"],
["--rpc-payment-address", "44SKxxLQw929wRF6BA9paQ1EWFshNnKhXM3qz6Mo3JGDE2YG3xyzVutMStEicxbQGRfrYvAAYxH6Fe8rnD56EaNwUiqhcwR", "--rpc-payment-difficulty", str(DIFFICULTY), "--rpc-payment-credits", "5000", "--offline"],
["--add-exclusive-node", "127.0.0.1:18283"],
["--add-exclusive-node", "127.0.0.1:18282"],
["--rpc-login", "md5_lover:Z1ON0101", "--offline"],
["--add-exclusive-node", "127.0.0.1:18283", "--add-exclusive-node", "127.0.0.1:18284", "--p2p-disable-encryption"],
["--add-exclusive-node", "127.0.0.1:18282", "--add-exclusive-node", "127.0.0.1:18284"],
["--rpc-login", "md5_lover:Z1ON0101", "--add-exclusive-node", "127.0,0,1:18282,no_encryption", "--add-exclusive-node", "127.0.0.1:18283"],
]
wallet_base = [builddir + "/bin/monero-wallet-rpc", "--wallet-dir", WALLET_DIRECTORY, "--rpc-bind-port", "wallet_port", "--rpc-ssl", "disabled", "--daemon-ssl", "disabled", "--log-level", "1", "--allow-mismatched-daemon-version"]
wallet_extra = [

View File

@ -45,6 +45,7 @@ class P2PTest():
self.mine(80)
self.test_p2p_reorg()
self.test_p2p_tx_propagation()
self.test_p2p_ssl()
def reset(self):
print('Resetting blockchain')
@ -78,6 +79,8 @@ class P2PTest():
daemon3 = Daemon(idx = 3)
# give sync some time
daemon2.stop_mining()
daemon3.stop_mining()
time.sleep(1)
res = daemon2.get_info()
@ -168,6 +171,8 @@ class P2PTest():
res = daemon.get_transaction_pool_hashes()
assert not 'tx_hashes' in res or len(res.tx_hashes) == 0
daemon2.stop_mining()
daemon3.stop_mining()
self.wallet.refresh()
res = self.wallet.get_balance()
@ -176,13 +181,36 @@ class P2PTest():
assert len(res.tx_hash) == 32*2
txid = res.tx_hash
time.sleep(5)
time.sleep(45)
for daemon in [daemon2, daemon3]:
res = daemon.get_transaction_pool_hashes()
assert len(res.tx_hashes) == 1
assert res.tx_hashes[0] == txid
def test_p2p_ssl(self):
print('Testing P2P SSL')
daemon2 = Daemon(idx = 2)
daemon3 = Daemon(idx = 3)
daemon4 = Daemon(idx = 4, username="md5_lover", password="Z1ON0101")
connections = daemon2.get_connections().connections
for connection in connections:
assert connection.ssl == 0
connections = daemon3.get_connections().connections
for connection in connections:
if connection.port == "18282":
assert connection.ssl == 0
elif connection.port == "18284":
assert connection.ssl == 2
connections = daemon4.get_connections().connections
for connection in connections:
if connection.port == "18282":
assert connection.ssl == 0
elif connection.port == "18283":
assert connection.ssl == 2
if __name__ == '__main__':
P2PTest().run_test()

View File

@ -47,7 +47,7 @@ namespace net_load_tests
{
struct test_connection_context : epee::net_utils::connection_context_base
{
test_connection_context(): epee::net_utils::connection_context_base(boost::uuids::nil_uuid(), {}, false, false), m_closed(false) {}
test_connection_context(): epee::net_utils::connection_context_base(boost::uuids::nil_uuid(), {}, false, epee::net_utils::ssl_support_t::e_ssl_support_disabled), m_closed(false) {}
static constexpr int handshake_command() noexcept { return 1001; }
static constexpr bool handshake_complete() noexcept { return true; }
size_t get_max_bytes(int command) const { return LEVIN_DEFAULT_MAX_PACKET_SIZE; }

View File

@ -372,14 +372,14 @@ TEST_F(positive_test_connection_to_levin_protocol_handler_calls, handler_process
epee::levin::bucket_head2 resp_head;
ASSERT_LT(sizeof(resp_head), send_data.size());
std::memcpy(std::addressof(resp_head), send_data.data(), sizeof(resp_head));
std::string out_data = send_data.substr(sizeof(resp_head));
std::string out_data = send_data.substr(sizeof(resp_head), expected_out_data.size());
// Check sent response
ASSERT_EQ(expected_out_data, out_data);
ASSERT_EQ(LEVIN_SIGNATURE, SWAP64LE(resp_head.m_signature));
ASSERT_EQ(expected_command, SWAP32LE(resp_head.m_command));
ASSERT_EQ(expected_return_code, SWAP32LE(resp_head.m_return_code));
ASSERT_EQ(expected_out_data.size(), SWAP64LE(resp_head.m_cb));
ASSERT_LE(expected_out_data.size(), SWAP64LE(resp_head.m_cb));
ASSERT_FALSE(resp_head.m_have_to_return_data);
ASSERT_EQ(SWAP32LE(LEVIN_PROTOCOL_VER_1), resp_head.m_protocol_version);
ASSERT_TRUE(0 != (SWAP32LE(resp_head.m_flags) & LEVIN_PACKET_RESPONSE));
@ -438,7 +438,7 @@ TEST_F(positive_test_connection_to_levin_protocol_handler_calls, handler_process
message.buffer.write(epee::to_span(in_data));
const epee::byte_slice noise = epee::levin::make_noise_notify(1024);
const epee::byte_slice notify = message.finalize_notify(expected_command);
const epee::byte_slice notify = message.finalize_notify(expected_command, false);
test_connection_ptr conn = create_connection();
@ -479,7 +479,7 @@ TEST_F(positive_test_connection_to_levin_protocol_handler_calls, handler_process
in_fragmented_data.buffer.put_n('c', 1024 * 4);
const epee::byte_slice noise = epee::levin::make_noise_notify(1024);
const epee::byte_slice notify = message.finalize_notify(expected_command);
const epee::byte_slice notify = message.finalize_notify(expected_command, false);
epee::byte_slice fragmented = epee::levin::make_fragmented_notify(noise.size(), expected_fragmented_command, std::move(in_fragmented_data));
EXPECT_EQ(5u, fragmented.size() / 1024);

View File

@ -177,7 +177,7 @@ namespace
handler_(std::addressof(endpoint_), connections, context_)
{
using base_type = epee::net_utils::connection_context_base;
static_cast<base_type&>(context_) = base_type{random_generator(), {}, is_incoming, false};
static_cast<base_type&>(context_) = base_type{random_generator(), {}, is_incoming, epee::net_utils::ssl_support_t::e_ssl_support_disabled};
context_.m_state = cryptonote::cryptonote_connection_context::state_normal;
handler_.after_init_connection();
}
@ -397,7 +397,7 @@ TEST(make_header, expect_return)
TEST(message_writer, invoke_with_empty_payload)
{
const epee::byte_slice message = epee::levin::message_writer{}.finalize_invoke(443);
const epee::byte_slice message = epee::levin::message_writer{}.finalize_invoke(443, false);
const epee::levin::bucket_head2 header =
epee::levin::make_header(443, 0, LEVIN_PACKET_REQUEST, true);
ASSERT_EQ(sizeof(header), message.size());
@ -412,7 +412,7 @@ TEST(message_writer, invoke_with_payload)
epee::levin::message_writer writer{};
writer.buffer.write(epee::to_span(bytes));
const epee::byte_slice message = writer.finalize_invoke(443);
const epee::byte_slice message = writer.finalize_invoke(443, false);
const epee::levin::bucket_head2 header =
epee::levin::make_header(443, bytes.size(), LEVIN_PACKET_REQUEST, true);
@ -423,7 +423,7 @@ TEST(message_writer, invoke_with_payload)
TEST(message_writer, notify_with_empty_payload)
{
const epee::byte_slice message = epee::levin::message_writer{}.finalize_notify(443);
const epee::byte_slice message = epee::levin::message_writer{}.finalize_notify(443, false);
const epee::levin::bucket_head2 header =
epee::levin::make_header(443, 0, LEVIN_PACKET_REQUEST, false);
ASSERT_EQ(sizeof(header), message.size());
@ -438,7 +438,7 @@ TEST(message_writer, notify_with_payload)
epee::levin::message_writer writer{};
writer.buffer.write(epee::to_span(bytes));
const epee::byte_slice message = writer.finalize_notify(443);
const epee::byte_slice message = writer.finalize_notify(443, false);
const epee::levin::bucket_head2 header =
epee::levin::make_header(443, bytes.size(), LEVIN_PACKET_REQUEST, false);
@ -449,7 +449,7 @@ TEST(message_writer, notify_with_payload)
TEST(message_writer, response_with_empty_payload)
{
const epee::byte_slice message = epee::levin::message_writer{}.finalize_response(443, 1);
const epee::byte_slice message = epee::levin::message_writer{}.finalize_response(443, 1, false);
epee::levin::bucket_head2 header =
epee::levin::make_header(443, 0, LEVIN_PACKET_RESPONSE, false);
header.m_return_code = SWAP32LE(1);
@ -465,7 +465,7 @@ TEST(message_writer, response_with_payload)
epee::levin::message_writer writer{};
writer.buffer.write(epee::to_span(bytes));
const epee::byte_slice message = writer.finalize_response(443, 6450);
const epee::byte_slice message = writer.finalize_response(443, 6450, false);
epee::levin::bucket_head2 header =
epee::levin::make_header(443, bytes.size(), LEVIN_PACKET_RESPONSE, false);
header.m_return_code = SWAP32LE(6450);
@ -480,9 +480,9 @@ TEST(message_writer, error)
epee::levin::message_writer writer{};
writer.buffer.clear();
EXPECT_THROW(writer.finalize_invoke(0), std::runtime_error);
EXPECT_THROW(writer.finalize_notify(0), std::runtime_error);
EXPECT_THROW(writer.finalize_response(0, 0), std::runtime_error);
EXPECT_THROW(writer.finalize_invoke(0, false), std::runtime_error);
EXPECT_THROW(writer.finalize_notify(0, false), std::runtime_error);
EXPECT_THROW(writer.finalize_response(0, 0, false), std::runtime_error);
}
TEST(make_noise, invalid)
@ -1068,7 +1068,7 @@ TEST_F(levin_notify, fluff_with_padding)
{
auto notification = receiver_.get_notification<cryptonote::NOTIFY_NEW_TRANSACTIONS>().second;
EXPECT_EQ(txs, notification.txs);
EXPECT_FALSE(notification._.empty());
EXPECT_TRUE(notification._.empty());
EXPECT_TRUE(notification.dandelionpp_fluff);
}
}
@ -1133,7 +1133,7 @@ TEST_F(levin_notify, stem_with_padding)
{
auto notification = receiver_.get_notification<cryptonote::NOTIFY_NEW_TRANSACTIONS>().second;
EXPECT_EQ(txs, notification.txs);
EXPECT_FALSE(notification._.empty());
EXPECT_TRUE(notification._.empty());
EXPECT_EQ(!is_stem, notification.dandelionpp_fluff);
}
@ -1196,7 +1196,7 @@ TEST_F(levin_notify, stem_no_outs_with_padding)
{
auto notification = receiver_.get_notification<cryptonote::NOTIFY_NEW_TRANSACTIONS>().second;
EXPECT_EQ(sorted_txs, notification.txs);
EXPECT_FALSE(notification._.empty());
EXPECT_TRUE(notification._.empty());
EXPECT_TRUE(notification.dandelionpp_fluff);
}
}
@ -1265,7 +1265,7 @@ TEST_F(levin_notify, local_with_padding)
{
auto notification = receiver_.get_notification<cryptonote::NOTIFY_NEW_TRANSACTIONS>().second;
EXPECT_EQ(their_txs, notification.txs);
EXPECT_FALSE(notification._.empty());
EXPECT_TRUE(notification._.empty());
EXPECT_EQ(!is_stem, notification.dandelionpp_fluff);
}
@ -1294,7 +1294,7 @@ TEST_F(levin_notify, local_with_padding)
EXPECT_EQ(1u, receiver_.notified_size());
auto notification = receiver_.get_notification<cryptonote::NOTIFY_NEW_TRANSACTIONS>().second;
EXPECT_EQ(my_txs, notification.txs);
EXPECT_FALSE(notification._.empty());
EXPECT_TRUE(notification._.empty());
EXPECT_TRUE(!notification.dandelionpp_fluff);
has_stemmed |= is_stem;
@ -1362,7 +1362,7 @@ TEST_F(levin_notify, forward_with_padding)
{
auto notification = receiver_.get_notification<cryptonote::NOTIFY_NEW_TRANSACTIONS>().second;
EXPECT_EQ(txs, notification.txs);
EXPECT_FALSE(notification._.empty());
EXPECT_TRUE(notification._.empty());
EXPECT_EQ(!is_stem, notification.dandelionpp_fluff);
}
@ -1755,7 +1755,7 @@ TEST_F(levin_notify, private_fluff_with_padding)
{
auto notification = receiver_.get_notification<cryptonote::NOTIFY_NEW_TRANSACTIONS>().second;
EXPECT_EQ(txs, notification.txs);
EXPECT_FALSE(notification._.empty());
EXPECT_TRUE(notification._.empty());
EXPECT_TRUE(notification.dandelionpp_fluff);
}
}
@ -1807,7 +1807,7 @@ TEST_F(levin_notify, private_stem_with_padding)
{
auto notification = receiver_.get_notification<cryptonote::NOTIFY_NEW_TRANSACTIONS>().second;
EXPECT_EQ(txs, notification.txs);
EXPECT_FALSE(notification._.empty());
EXPECT_TRUE(notification._.empty());
EXPECT_TRUE(notification.dandelionpp_fluff);
}
}
@ -1859,7 +1859,7 @@ TEST_F(levin_notify, private_local_with_padding)
{
auto notification = receiver_.get_notification<cryptonote::NOTIFY_NEW_TRANSACTIONS>().second;
EXPECT_EQ(txs, notification.txs);
EXPECT_FALSE(notification._.empty());
EXPECT_TRUE(notification._.empty());
EXPECT_TRUE(notification.dandelionpp_fluff);
}
}
@ -1911,7 +1911,7 @@ TEST_F(levin_notify, private_forward_with_padding)
{
auto notification = receiver_.get_notification<cryptonote::NOTIFY_NEW_TRANSACTIONS>().second;
EXPECT_EQ(txs, notification.txs);
EXPECT_FALSE(notification._.empty());
EXPECT_TRUE(notification._.empty());
EXPECT_TRUE(notification.dandelionpp_fluff);
}
}
@ -2385,7 +2385,7 @@ TEST_F(levin_notify, command_max_bytes)
{
epee::levin::message_writer dest{};
dest.buffer.write(epee::to_span(payload));
bytes = dest.finalize_notify(ping_command);
bytes = dest.finalize_notify(ping_command, false);
}
EXPECT_EQ(1, get_connections().send(bytes.clone(), contexts_.front().get_id()));
@ -2401,7 +2401,7 @@ TEST_F(levin_notify, command_max_bytes)
payload.push_back('h');
epee::levin::message_writer dest{};
dest.buffer.write(epee::to_span(payload));
bytes = dest.finalize_notify(ping_command);
bytes = dest.finalize_notify(ping_command, true);
}
EXPECT_EQ(1, get_connections().send(std::move(bytes), contexts_.front().get_id()));

View File

@ -530,14 +530,14 @@ TEST(cryptonote_protocol_handler, race_condition)
}
virtual bool invoke_notify_to_peer(int command, epee::levin::message_writer in, const contexts::basic& context) override {
if (shared_state)
return shared_state->send(in.finalize_notify(command), context.m_connection_id);
return shared_state->send(in.finalize_notify(command, true), context.m_connection_id);
else
return {};
}
virtual bool relay_notify_to_list(int command, epee::levin::message_writer in, connections_t connections) override {
if (shared_state) {
for (auto &e: connections)
shared_state->send(in.finalize_notify(command), e.second);
shared_state->send(in.finalize_notify(command, true), e.second);
}
return {};
}
@ -1090,6 +1090,7 @@ TEST(node_server, race_condition)
conn->get_context(context);
event_t handshaked;
typename messages::handshake::request_t msg{{
std::string{},
::config::NETWORK_ID,
58080,
}};

View File

@ -111,13 +111,13 @@ TEST(peerlist_storage, store)
std::string buffer{};
{
nodetool::peerlist_types types{};
types.white.push_back({epee::net_utils::ipv4_network_address{1000, 10}, 44, 55});
types.white.push_back({net::tor_address::unknown(), 64, 75});
types.gray.push_back({net::tor_address::unknown(), 99, 88});
types.gray.push_back({epee::net_utils::ipv4_network_address{2000, 20}, 84, 45});
types.anchor.push_back({epee::net_utils::ipv4_network_address{999, 654}, 444, 555});
types.anchor.push_back({net::tor_address::unknown(), 14, 33});
types.anchor.push_back({net::tor_address::unknown(), 24, 22});
types.white.push_back({epee::net_utils::ipv4_network_address{1000, 10}, {}, 44, 55});
types.white.push_back({net::tor_address::unknown(), {}, 64, 75});
types.gray.push_back({net::tor_address::unknown(), {}, 99, 88});
types.gray.push_back({epee::net_utils::ipv4_network_address{2000, 20}, {}, 84, 45});
types.anchor.push_back({epee::net_utils::ipv4_network_address{999, 654}, {}, 444, 555});
types.anchor.push_back({net::tor_address::unknown(), {}, 14, 33});
types.anchor.push_back({net::tor_address::unknown(), {}, 24, 22});
std::ostringstream stream{};
EXPECT_TRUE(peers.store(stream, types));