Merge pull request #5363

515ac29 p2p: store network address directly in blocked host list (moneromooo-monero)
65c4004 allow blocking whole subnets (moneromooo-monero)
This commit is contained in:
luigi1111 2019-07-24 13:47:41 -05:00
commit 1880c1a582
No known key found for this signature in database
GPG key ID: F4ACA0183641E010
19 changed files with 425 additions and 46 deletions

View file

@ -70,7 +70,7 @@ namespace net_utils
struct i_connection_filter struct i_connection_filter
{ {
virtual bool is_remote_host_allowed(const epee::net_utils::network_address &address)=0; virtual bool is_remote_host_allowed(const epee::net_utils::network_address &address, time_t *t = NULL)=0;
protected: protected:
virtual ~i_connection_filter(){} virtual ~i_connection_filter(){}
}; };

View file

@ -41,7 +41,7 @@
#define MONERO_DEFAULT_LOG_CATEGORY "net" #define MONERO_DEFAULT_LOG_CATEGORY "net"
#ifndef MAKE_IP #ifndef MAKE_IP
#define MAKE_IP( a1, a2, a3, a4 ) (a1|(a2<<8)|(a3<<16)|(a4<<24)) #define MAKE_IP( a1, a2, a3, a4 ) (a1|(a2<<8)|(a3<<16)|(((uint32_t)a4)<<24))
#endif #endif
#if BOOST_VERSION >= 107000 #if BOOST_VERSION >= 107000
@ -107,6 +107,53 @@ namespace net_utils
inline bool operator>=(const ipv4_network_address& lhs, const ipv4_network_address& rhs) noexcept inline bool operator>=(const ipv4_network_address& lhs, const ipv4_network_address& rhs) noexcept
{ return !lhs.less(rhs); } { return !lhs.less(rhs); }
class ipv4_network_subnet
{
uint32_t m_ip;
uint8_t m_mask;
public:
constexpr ipv4_network_subnet() noexcept
: ipv4_network_subnet(0, 0)
{}
constexpr ipv4_network_subnet(uint32_t ip, uint8_t mask) noexcept
: m_ip(ip), m_mask(mask) {}
bool equal(const ipv4_network_subnet& other) const noexcept;
bool less(const ipv4_network_subnet& other) const noexcept;
constexpr bool is_same_host(const ipv4_network_subnet& other) const noexcept
{ return subnet() == other.subnet(); }
bool matches(const ipv4_network_address &address) const;
constexpr uint32_t subnet() const noexcept { return m_ip & ~(0xffffffffull << m_mask); }
std::string str() const;
std::string host_str() const;
bool is_loopback() const;
bool is_local() const;
static constexpr address_type get_type_id() noexcept { return address_type::invalid; }
static constexpr zone get_zone() noexcept { return zone::public_; }
static constexpr bool is_blockable() noexcept { return true; }
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(m_ip)
KV_SERIALIZE(m_mask)
END_KV_SERIALIZE_MAP()
};
inline bool operator==(const ipv4_network_subnet& lhs, const ipv4_network_subnet& rhs) noexcept
{ return lhs.equal(rhs); }
inline bool operator!=(const ipv4_network_subnet& lhs, const ipv4_network_subnet& rhs) noexcept
{ return !lhs.equal(rhs); }
inline bool operator<(const ipv4_network_subnet& lhs, const ipv4_network_subnet& rhs) noexcept
{ return lhs.less(rhs); }
inline bool operator<=(const ipv4_network_subnet& lhs, const ipv4_network_subnet& rhs) noexcept
{ return !rhs.less(lhs); }
inline bool operator>(const ipv4_network_subnet& lhs, const ipv4_network_subnet& rhs) noexcept
{ return rhs.less(lhs); }
inline bool operator>=(const ipv4_network_subnet& lhs, const ipv4_network_subnet& rhs) noexcept
{ return !lhs.less(rhs); }
class network_address class network_address
{ {
struct interface struct interface

View file

@ -22,6 +22,24 @@ namespace epee { namespace net_utils
bool ipv4_network_address::is_local() const { return net_utils::is_ip_local(ip()); } bool ipv4_network_address::is_local() const { return net_utils::is_ip_local(ip()); }
bool ipv4_network_subnet::equal(const ipv4_network_subnet& other) const noexcept
{ return is_same_host(other) && m_mask == other.m_mask; }
bool ipv4_network_subnet::less(const ipv4_network_subnet& other) const noexcept
{ return subnet() < other.subnet() ? true : (other.subnet() < subnet() ? false : (m_mask < other.m_mask)); }
std::string ipv4_network_subnet::str() const
{ return string_tools::get_ip_string_from_int32(subnet()) + "/" + std::to_string(m_mask); }
std::string ipv4_network_subnet::host_str() const { return string_tools::get_ip_string_from_int32(subnet()) + "/" + std::to_string(m_mask); }
bool ipv4_network_subnet::is_loopback() const { return net_utils::is_ip_loopback(subnet()); }
bool ipv4_network_subnet::is_local() const { return net_utils::is_ip_local(subnet()); }
bool ipv4_network_subnet::matches(const ipv4_network_address &address) const
{
return (address.ip() & ~(0xffffffffull << m_mask)) == subnet();
}
bool network_address::equal(const network_address& other) const bool network_address::equal(const network_address& other) const
{ {
// clang typeid workaround // clang typeid workaround

View file

@ -596,6 +596,13 @@ bool t_command_parser_executor::unban(const std::vector<std::string>& args)
return m_executor.unban(ip); return m_executor.unban(ip);
} }
bool t_command_parser_executor::banned(const std::vector<std::string>& args)
{
if (args.size() != 1) return false;
std::string address = args[0];
return m_executor.banned(address);
}
bool t_command_parser_executor::flush_txpool(const std::vector<std::string>& args) bool t_command_parser_executor::flush_txpool(const std::vector<std::string>& args)
{ {
if (args.size() > 1) return false; if (args.size() > 1) return false;

View file

@ -127,6 +127,8 @@ public:
bool unban(const std::vector<std::string>& args); bool unban(const std::vector<std::string>& args);
bool banned(const std::vector<std::string>& args);
bool flush_txpool(const std::vector<std::string>& args); bool flush_txpool(const std::vector<std::string>& args);
bool output_histogram(const std::vector<std::string>& args); bool output_histogram(const std::vector<std::string>& args);

View file

@ -243,9 +243,15 @@ t_command_server::t_command_server(
m_command_lookup.set_handler( m_command_lookup.set_handler(
"unban" "unban"
, std::bind(&t_command_parser_executor::unban, &m_parser, p::_1) , std::bind(&t_command_parser_executor::unban, &m_parser, p::_1)
, "unban <IP>" , "unban <address>"
, "Unban a given <IP>." , "Unban a given <IP>."
); );
m_command_lookup.set_handler(
"banned"
, std::bind(&t_command_parser_executor::banned, &m_parser, p::_1)
, "banned <address>"
, "Check whether an <address> is banned."
);
m_command_lookup.set_handler( m_command_lookup.set_handler(
"flush_txpool" "flush_txpool"
, std::bind(&t_command_parser_executor::flush_txpool, &m_parser, p::_1) , std::bind(&t_command_parser_executor::flush_txpool, &m_parser, p::_1)

View file

@ -1641,14 +1641,14 @@ bool t_rpc_command_executor::print_bans()
for (auto i = res.bans.begin(); i != res.bans.end(); ++i) for (auto i = res.bans.begin(); i != res.bans.end(); ++i)
{ {
tools::msg_writer() << epee::string_tools::get_ip_string_from_int32(i->ip) << " banned for " << i->seconds << " seconds"; tools::msg_writer() << i->host << " banned for " << i->seconds << " seconds";
} }
return true; return true;
} }
bool t_rpc_command_executor::ban(const std::string &ip, time_t seconds) bool t_rpc_command_executor::ban(const std::string &address, time_t seconds)
{ {
cryptonote::COMMAND_RPC_SETBANS::request req; cryptonote::COMMAND_RPC_SETBANS::request req;
cryptonote::COMMAND_RPC_SETBANS::response res; cryptonote::COMMAND_RPC_SETBANS::response res;
@ -1656,11 +1656,8 @@ bool t_rpc_command_executor::ban(const std::string &ip, time_t seconds)
epee::json_rpc::error error_resp; epee::json_rpc::error error_resp;
cryptonote::COMMAND_RPC_SETBANS::ban ban; cryptonote::COMMAND_RPC_SETBANS::ban ban;
if (!epee::string_tools::get_ip_int32_from_string(ban.ip, ip)) ban.host = address;
{ ban.ip = 0;
tools::fail_msg_writer() << "Invalid IP";
return true;
}
ban.ban = true; ban.ban = true;
ban.seconds = seconds; ban.seconds = seconds;
req.bans.push_back(ban); req.bans.push_back(ban);
@ -1684,7 +1681,7 @@ bool t_rpc_command_executor::ban(const std::string &ip, time_t seconds)
return true; return true;
} }
bool t_rpc_command_executor::unban(const std::string &ip) bool t_rpc_command_executor::unban(const std::string &address)
{ {
cryptonote::COMMAND_RPC_SETBANS::request req; cryptonote::COMMAND_RPC_SETBANS::request req;
cryptonote::COMMAND_RPC_SETBANS::response res; cryptonote::COMMAND_RPC_SETBANS::response res;
@ -1692,11 +1689,8 @@ bool t_rpc_command_executor::unban(const std::string &ip)
epee::json_rpc::error error_resp; epee::json_rpc::error error_resp;
cryptonote::COMMAND_RPC_SETBANS::ban ban; cryptonote::COMMAND_RPC_SETBANS::ban ban;
if (!epee::string_tools::get_ip_int32_from_string(ban.ip, ip)) ban.host = address;
{ ban.ip = 0;
tools::fail_msg_writer() << "Invalid IP";
return true;
}
ban.ban = false; ban.ban = false;
ban.seconds = 0; ban.seconds = 0;
req.bans.push_back(ban); req.bans.push_back(ban);
@ -1720,6 +1714,39 @@ bool t_rpc_command_executor::unban(const std::string &ip)
return true; return true;
} }
bool t_rpc_command_executor::banned(const std::string &address)
{
cryptonote::COMMAND_RPC_BANNED::request req;
cryptonote::COMMAND_RPC_BANNED::response res;
std::string fail_message = "Unsuccessful";
epee::json_rpc::error error_resp;
req.address = address;
if (m_is_rpc)
{
if (!m_rpc_client->json_rpc_request(req, res, "banned", fail_message.c_str()))
{
return true;
}
}
else
{
if (!m_rpc_server->on_banned(req, res, error_resp) || res.status != CORE_RPC_STATUS_OK)
{
tools::fail_msg_writer() << make_error(fail_message, res.status);
return true;
}
}
if (res.banned)
tools::msg_writer() << address << " is banned for " << res.seconds << " seconds";
else
tools::msg_writer() << address << " is not banned";
return true;
}
bool t_rpc_command_executor::flush_txpool(const std::string &txid) bool t_rpc_command_executor::flush_txpool(const std::string &txid)
{ {
cryptonote::COMMAND_RPC_FLUSH_TRANSACTION_POOL::request req; cryptonote::COMMAND_RPC_FLUSH_TRANSACTION_POOL::request req;

View file

@ -137,9 +137,11 @@ public:
bool print_bans(); bool print_bans();
bool ban(const std::string &ip, time_t seconds); bool ban(const std::string &address, time_t seconds);
bool unban(const std::string &ip); bool unban(const std::string &address);
bool banned(const std::string &address);
bool flush_txpool(const std::string &txid); bool flush_txpool(const std::string &txid);

View file

@ -42,7 +42,8 @@ namespace net
invalid_i2p_address, invalid_i2p_address,
invalid_port, //!< Outside of 0-65535 range invalid_port, //!< Outside of 0-65535 range
invalid_tor_address,//!< Invalid base32 or length invalid_tor_address,//!< Invalid base32 or length
unsupported_address //!< Type not supported by `get_network_address` unsupported_address,//!< Type not supported by `get_network_address`
invalid_mask, //!< Outside of 0-32 range
}; };
//! \return `std::error_category` for `net` namespace. //! \return `std::error_category` for `net` namespace.

View file

@ -58,4 +58,27 @@ namespace net
return {epee::net_utils::ipv4_network_address{ip, port}}; return {epee::net_utils::ipv4_network_address{ip, port}};
return make_error_code(net::error::unsupported_address); return make_error_code(net::error::unsupported_address);
} }
expect<epee::net_utils::ipv4_network_subnet>
get_ipv4_subnet_address(const boost::string_ref address, bool allow_implicit_32)
{
uint32_t mask = 32;
const boost::string_ref::size_type slash = address.find_first_of('/');
if (slash != boost::string_ref::npos)
{
if (!epee::string_tools::get_xtype_from_string(mask, std::string{address.substr(slash + 1)}))
return make_error_code(net::error::invalid_mask);
if (mask > 32)
return make_error_code(net::error::invalid_mask);
}
else if (!allow_implicit_32)
return make_error_code(net::error::invalid_mask);
std::uint32_t ip = 0;
boost::string_ref S(address.data(), slash != boost::string_ref::npos ? slash : address.size());
if (!epee::string_tools::get_ip_int32_from_string(ip, std::string(S)))
return make_error_code(net::error::invalid_host);
return {epee::net_utils::ipv4_network_subnet{ip, (uint8_t)mask}};
}
} }

View file

@ -50,5 +50,18 @@ namespace net
*/ */
expect<epee::net_utils::network_address> expect<epee::net_utils::network_address>
get_network_address(boost::string_ref address, std::uint16_t default_port); get_network_address(boost::string_ref address, std::uint16_t default_port);
/*!
Identifies an IPv4 subnet in CIDR notatioa and returns it as a generic
`network_address`. If the type is unsupported, it might be a hostname,
and `error() == net::error::kUnsupportedAddress` is returned.
\param address An ipv4 address.
\param allow_implicit_32 whether to accept "raw" IPv4 addresses, with CIDR notation
\return A tor or IPv4 address, else error.
*/
expect<epee::net_utils::ipv4_network_subnet>
get_ipv4_subnet_address(boost::string_ref address, bool allow_implicit_32 = false);
} }

View file

@ -248,7 +248,11 @@ namespace nodetool
void change_max_in_public_peers(size_t count); void change_max_in_public_peers(size_t count);
virtual bool block_host(const epee::net_utils::network_address &adress, time_t seconds = P2P_IP_BLOCKTIME); virtual bool block_host(const epee::net_utils::network_address &adress, time_t seconds = P2P_IP_BLOCKTIME);
virtual bool unblock_host(const epee::net_utils::network_address &address); virtual bool unblock_host(const epee::net_utils::network_address &address);
virtual std::map<std::string, time_t> get_blocked_hosts() { CRITICAL_REGION_LOCAL(m_blocked_hosts_lock); return m_blocked_hosts; } virtual bool block_subnet(const epee::net_utils::ipv4_network_subnet &subnet, time_t seconds = P2P_IP_BLOCKTIME);
virtual bool unblock_subnet(const epee::net_utils::ipv4_network_subnet &subnet);
virtual bool is_host_blocked(const epee::net_utils::network_address &address, time_t *seconds) { CRITICAL_REGION_LOCAL(m_blocked_hosts_lock); return !is_remote_host_allowed(address, seconds); }
virtual std::map<epee::net_utils::network_address, time_t> get_blocked_hosts() { CRITICAL_REGION_LOCAL(m_blocked_hosts_lock); return m_blocked_hosts; }
virtual std::map<epee::net_utils::ipv4_network_subnet, time_t> get_blocked_subnets() { CRITICAL_REGION_LOCAL(m_blocked_hosts_lock); return m_blocked_subnets; }
virtual void add_used_stripe_peer(const typename t_payload_net_handler::connection_context &context); virtual void add_used_stripe_peer(const typename t_payload_net_handler::connection_context &context);
virtual void remove_used_stripe_peer(const typename t_payload_net_handler::connection_context &context); virtual void remove_used_stripe_peer(const typename t_payload_net_handler::connection_context &context);
@ -319,7 +323,7 @@ namespace nodetool
virtual bool for_connection(const boost::uuids::uuid&, std::function<bool(typename t_payload_net_handler::connection_context&, peerid_type, uint32_t)> f); virtual bool for_connection(const boost::uuids::uuid&, std::function<bool(typename t_payload_net_handler::connection_context&, peerid_type, uint32_t)> f);
virtual bool add_host_fail(const epee::net_utils::network_address &address); virtual bool add_host_fail(const epee::net_utils::network_address &address);
//----------------- i_connection_filter -------------------------------------------------------- //----------------- i_connection_filter --------------------------------------------------------
virtual bool is_remote_host_allowed(const epee::net_utils::network_address &address); virtual bool is_remote_host_allowed(const epee::net_utils::network_address &address, time_t *t = NULL);
//----------------------------------------------------------------------------------------------- //-----------------------------------------------------------------------------------------------
bool parse_peer_from_string(epee::net_utils::network_address& pe, const std::string& node_addr, uint16_t default_port = 0); bool parse_peer_from_string(epee::net_utils::network_address& pe, const std::string& node_addr, uint16_t default_port = 0);
bool handle_command_line( bool handle_command_line(
@ -461,8 +465,9 @@ namespace nodetool
std::map<epee::net_utils::network_address, time_t> m_conn_fails_cache; std::map<epee::net_utils::network_address, time_t> m_conn_fails_cache;
epee::critical_section m_conn_fails_cache_lock; epee::critical_section m_conn_fails_cache_lock;
epee::critical_section m_blocked_hosts_lock; epee::critical_section m_blocked_hosts_lock; // for both hosts and subnets
std::map<std::string, time_t> m_blocked_hosts; std::map<epee::net_utils::network_address, time_t> m_blocked_hosts;
std::map<epee::net_utils::ipv4_network_subnet, time_t> m_blocked_subnets;
epee::critical_section m_host_fails_score_lock; epee::critical_section m_host_fails_score_lock;
std::map<std::string, uint64_t> m_host_fails_score; std::map<std::string, uint64_t> m_host_fails_score;

View file

@ -155,20 +155,56 @@ namespace nodetool
} }
//----------------------------------------------------------------------------------- //-----------------------------------------------------------------------------------
template<class t_payload_net_handler> template<class t_payload_net_handler>
bool node_server<t_payload_net_handler>::is_remote_host_allowed(const epee::net_utils::network_address &address) bool node_server<t_payload_net_handler>::is_remote_host_allowed(const epee::net_utils::network_address &address, time_t *t)
{ {
CRITICAL_REGION_LOCAL(m_blocked_hosts_lock); CRITICAL_REGION_LOCAL(m_blocked_hosts_lock);
auto it = m_blocked_hosts.find(address.host_str());
if(it == m_blocked_hosts.end()) const time_t now = time(nullptr);
return true;
if(time(nullptr) >= it->second) // look in the hosts list
auto it = m_blocked_hosts.find(address);
if (it != m_blocked_hosts.end())
{
if (now >= it->second)
{ {
m_blocked_hosts.erase(it); m_blocked_hosts.erase(it);
MCLOG_CYAN(el::Level::Info, "global", "Host " << address.host_str() << " unblocked."); MCLOG_CYAN(el::Level::Info, "global", "Host " << address.host_str() << " unblocked.");
return true; it = m_blocked_hosts.end();
} }
else
{
if (t)
*t = it->second - now;
return false; return false;
} }
}
// manually loop in subnets
if (address.get_type_id() == epee::net_utils::address_type::ipv4)
{
auto ipv4_address = address.template as<epee::net_utils::ipv4_network_address>();
std::map<epee::net_utils::ipv4_network_subnet, time_t>::iterator it;
for (it = m_blocked_subnets.begin(); it != m_blocked_subnets.end(); )
{
if (now >= it->second)
{
it = m_blocked_subnets.erase(it);
MCLOG_CYAN(el::Level::Info, "global", "Subnet " << it->first.host_str() << " unblocked.");
continue;
}
if (it->first.matches(ipv4_address))
{
if (t)
*t = it->second - now;
return false;
}
++it;
}
}
// not found in hosts or subnets, allowed
return true;
}
//----------------------------------------------------------------------------------- //-----------------------------------------------------------------------------------
template<class t_payload_net_handler> template<class t_payload_net_handler>
bool node_server<t_payload_net_handler>::block_host(const epee::net_utils::network_address &addr, time_t seconds) bool node_server<t_payload_net_handler>::block_host(const epee::net_utils::network_address &addr, time_t seconds)
@ -184,7 +220,7 @@ namespace nodetool
limit = std::numeric_limits<time_t>::max(); limit = std::numeric_limits<time_t>::max();
else else
limit = now + seconds; limit = now + seconds;
m_blocked_hosts[addr.host_str()] = limit; m_blocked_hosts[addr] = limit;
// drop any connection to that address. This should only have to look into // drop any connection to that address. This should only have to look into
// the zone related to the connection, but really make sure everything is // the zone related to the connection, but really make sure everything is
@ -214,7 +250,7 @@ namespace nodetool
bool node_server<t_payload_net_handler>::unblock_host(const epee::net_utils::network_address &address) bool node_server<t_payload_net_handler>::unblock_host(const epee::net_utils::network_address &address)
{ {
CRITICAL_REGION_LOCAL(m_blocked_hosts_lock); CRITICAL_REGION_LOCAL(m_blocked_hosts_lock);
auto i = m_blocked_hosts.find(address.host_str()); auto i = m_blocked_hosts.find(address);
if (i == m_blocked_hosts.end()) if (i == m_blocked_hosts.end())
return false; return false;
m_blocked_hosts.erase(i); m_blocked_hosts.erase(i);
@ -223,6 +259,58 @@ namespace nodetool
} }
//----------------------------------------------------------------------------------- //-----------------------------------------------------------------------------------
template<class t_payload_net_handler> template<class t_payload_net_handler>
bool node_server<t_payload_net_handler>::block_subnet(const epee::net_utils::ipv4_network_subnet &subnet, time_t seconds)
{
const time_t now = time(nullptr);
CRITICAL_REGION_LOCAL(m_blocked_hosts_lock);
time_t limit;
if (now > std::numeric_limits<time_t>::max() - seconds)
limit = std::numeric_limits<time_t>::max();
else
limit = now + seconds;
m_blocked_subnets[subnet] = limit;
// drop any connection to that subnet. This should only have to look into
// the zone related to the connection, but really make sure everything is
// swept ...
std::vector<boost::uuids::uuid> conns;
for(auto& zone : m_network_zones)
{
zone.second.m_net_server.get_config_object().foreach_connection([&](const p2p_connection_context& cntxt)
{
if (cntxt.m_remote_address.get_type_id() != epee::net_utils::ipv4_network_address::get_type_id())
return true;
auto ipv4_address = cntxt.m_remote_address.template as<epee::net_utils::ipv4_network_address>();
if (subnet.matches(ipv4_address))
{
conns.push_back(cntxt.m_connection_id);
}
return true;
});
for (const auto &c: conns)
zone.second.m_net_server.get_config_object().close(c);
conns.clear();
}
MCLOG_CYAN(el::Level::Info, "global", "Subnet " << subnet.host_str() << " blocked.");
return true;
}
//-----------------------------------------------------------------------------------
template<class t_payload_net_handler>
bool node_server<t_payload_net_handler>::unblock_subnet(const epee::net_utils::ipv4_network_subnet &subnet)
{
CRITICAL_REGION_LOCAL(m_blocked_hosts_lock);
auto i = m_blocked_subnets.find(subnet);
if (i == m_blocked_subnets.end())
return false;
m_blocked_subnets.erase(i);
MCLOG_CYAN(el::Level::Info, "global", "Subnet " << subnet.host_str() << " unblocked.");
return true;
}
//-----------------------------------------------------------------------------------
template<class t_payload_net_handler>
bool node_server<t_payload_net_handler>::add_host_fail(const epee::net_utils::network_address &address) bool node_server<t_payload_net_handler>::add_host_fail(const epee::net_utils::network_address &address)
{ {
if(!address.is_blockable()) if(!address.is_blockable())

View file

@ -56,7 +56,8 @@ namespace nodetool
virtual bool for_connection(const boost::uuids::uuid&, std::function<bool(t_connection_context&, peerid_type, uint32_t)> f)=0; virtual bool for_connection(const boost::uuids::uuid&, std::function<bool(t_connection_context&, peerid_type, uint32_t)> f)=0;
virtual bool block_host(const epee::net_utils::network_address &address, time_t seconds = 0)=0; virtual bool block_host(const epee::net_utils::network_address &address, time_t seconds = 0)=0;
virtual bool unblock_host(const epee::net_utils::network_address &address)=0; virtual bool unblock_host(const epee::net_utils::network_address &address)=0;
virtual std::map<std::string, time_t> get_blocked_hosts()=0; virtual std::map<epee::net_utils::network_address, time_t> get_blocked_hosts()=0;
virtual std::map<epee::net_utils::ipv4_network_subnet, time_t> get_blocked_subnets()=0;
virtual bool add_host_fail(const epee::net_utils::network_address &address)=0; virtual bool add_host_fail(const epee::net_utils::network_address &address)=0;
virtual void add_used_stripe_peer(const t_connection_context &context)=0; virtual void add_used_stripe_peer(const t_connection_context &context)=0;
virtual void remove_used_stripe_peer(const t_connection_context &context)=0; virtual void remove_used_stripe_peer(const t_connection_context &context)=0;
@ -112,9 +113,13 @@ namespace nodetool
{ {
return true; return true;
} }
virtual std::map<std::string, time_t> get_blocked_hosts() virtual std::map<epee::net_utils::network_address, time_t> get_blocked_hosts()
{ {
return std::map<std::string, time_t>(); return std::map<epee::net_utils::network_address, time_t>();
}
virtual std::map<epee::net_utils::ipv4_network_subnet, time_t> get_blocked_subnets()
{
return std::map<epee::net_utils::ipv4_network_subnet, time_t>();
} }
virtual bool add_host_fail(const epee::net_utils::network_address &address) virtual bool add_host_fail(const epee::net_utils::network_address &address)
{ {

View file

@ -1772,20 +1772,60 @@ namespace cryptonote
PERF_TIMER(on_get_bans); PERF_TIMER(on_get_bans);
auto now = time(nullptr); auto now = time(nullptr);
std::map<std::string, time_t> blocked_hosts = m_p2p.get_blocked_hosts(); std::map<epee::net_utils::network_address, time_t> blocked_hosts = m_p2p.get_blocked_hosts();
for (std::map<std::string, time_t>::const_iterator i = blocked_hosts.begin(); i != blocked_hosts.end(); ++i) for (std::map<epee::net_utils::network_address, time_t>::const_iterator i = blocked_hosts.begin(); i != blocked_hosts.end(); ++i)
{ {
if (i->second > now) { if (i->second > now) {
COMMAND_RPC_GETBANS::ban b; COMMAND_RPC_GETBANS::ban b;
b.host = i->first; b.host = i->first.host_str();
b.ip = 0; b.ip = 0;
uint32_t ip; uint32_t ip;
if (epee::string_tools::get_ip_int32_from_string(ip, i->first)) if (epee::string_tools::get_ip_int32_from_string(ip, b.host))
b.ip = ip; b.ip = ip;
b.seconds = i->second - now; b.seconds = i->second - now;
res.bans.push_back(b); res.bans.push_back(b);
} }
} }
std::map<epee::net_utils::ipv4_network_subnet, time_t> blocked_subnets = m_p2p.get_blocked_subnets();
for (std::map<epee::net_utils::ipv4_network_subnet, time_t>::const_iterator i = blocked_subnets.begin(); i != blocked_subnets.end(); ++i)
{
if (i->second > now) {
COMMAND_RPC_GETBANS::ban b;
b.host = i->first.host_str();
b.ip = 0;
b.seconds = i->second - now;
res.bans.push_back(b);
}
}
res.status = CORE_RPC_STATUS_OK;
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::on_banned(const COMMAND_RPC_BANNED::request& req, COMMAND_RPC_BANNED::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx)
{
PERF_TIMER(on_banned);
auto na_parsed = net::get_network_address(req.address, 0);
if (!na_parsed)
{
error_resp.code = CORE_RPC_ERROR_CODE_WRONG_PARAM;
error_resp.message = "Unsupported host type";
return false;
}
epee::net_utils::network_address na = std::move(*na_parsed);
time_t seconds;
if (m_p2p.is_host_blocked(na, &seconds))
{
res.banned = true;
res.seconds = seconds;
}
else
{
res.banned = false;
res.seconds = 0;
}
res.status = CORE_RPC_STATUS_OK; res.status = CORE_RPC_STATUS_OK;
return true; return true;
@ -1798,13 +1838,29 @@ namespace cryptonote
for (auto i = req.bans.begin(); i != req.bans.end(); ++i) for (auto i = req.bans.begin(); i != req.bans.end(); ++i)
{ {
epee::net_utils::network_address na; epee::net_utils::network_address na;
// try subnet first
if (!i->host.empty())
{
auto ns_parsed = net::get_ipv4_subnet_address(i->host);
if (ns_parsed)
{
if (i->ban)
m_p2p.block_subnet(*ns_parsed, i->seconds);
else
m_p2p.unblock_subnet(*ns_parsed);
continue;
}
}
// then host
if (!i->host.empty()) if (!i->host.empty())
{ {
auto na_parsed = net::get_network_address(i->host, 0); auto na_parsed = net::get_network_address(i->host, 0);
if (!na_parsed) if (!na_parsed)
{ {
error_resp.code = CORE_RPC_ERROR_CODE_WRONG_PARAM; error_resp.code = CORE_RPC_ERROR_CODE_WRONG_PARAM;
error_resp.message = "Unsupported host type"; error_resp.message = "Unsupported host/subnet type";
return false; return false;
} }
na = std::move(*na_parsed); na = std::move(*na_parsed);

View file

@ -154,6 +154,7 @@ namespace cryptonote
MAP_JON_RPC_WE("hard_fork_info", on_hard_fork_info, COMMAND_RPC_HARD_FORK_INFO) MAP_JON_RPC_WE("hard_fork_info", on_hard_fork_info, COMMAND_RPC_HARD_FORK_INFO)
MAP_JON_RPC_WE_IF("set_bans", on_set_bans, COMMAND_RPC_SETBANS, !m_restricted) MAP_JON_RPC_WE_IF("set_bans", on_set_bans, COMMAND_RPC_SETBANS, !m_restricted)
MAP_JON_RPC_WE_IF("get_bans", on_get_bans, COMMAND_RPC_GETBANS, !m_restricted) MAP_JON_RPC_WE_IF("get_bans", on_get_bans, COMMAND_RPC_GETBANS, !m_restricted)
MAP_JON_RPC_WE_IF("banned", on_banned, COMMAND_RPC_BANNED, !m_restricted)
MAP_JON_RPC_WE_IF("flush_txpool", on_flush_txpool, COMMAND_RPC_FLUSH_TRANSACTION_POOL, !m_restricted) MAP_JON_RPC_WE_IF("flush_txpool", on_flush_txpool, COMMAND_RPC_FLUSH_TRANSACTION_POOL, !m_restricted)
MAP_JON_RPC_WE("get_output_histogram", on_get_output_histogram, COMMAND_RPC_GET_OUTPUT_HISTOGRAM) MAP_JON_RPC_WE("get_output_histogram", on_get_output_histogram, COMMAND_RPC_GET_OUTPUT_HISTOGRAM)
MAP_JON_RPC_WE("get_version", on_get_version, COMMAND_RPC_GET_VERSION) MAP_JON_RPC_WE("get_version", on_get_version, COMMAND_RPC_GET_VERSION)
@ -220,6 +221,7 @@ namespace cryptonote
bool on_hard_fork_info(const COMMAND_RPC_HARD_FORK_INFO::request& req, COMMAND_RPC_HARD_FORK_INFO::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL); bool on_hard_fork_info(const COMMAND_RPC_HARD_FORK_INFO::request& req, COMMAND_RPC_HARD_FORK_INFO::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL);
bool on_set_bans(const COMMAND_RPC_SETBANS::request& req, COMMAND_RPC_SETBANS::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL); bool on_set_bans(const COMMAND_RPC_SETBANS::request& req, COMMAND_RPC_SETBANS::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL);
bool on_get_bans(const COMMAND_RPC_GETBANS::request& req, COMMAND_RPC_GETBANS::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL); bool on_get_bans(const COMMAND_RPC_GETBANS::request& req, COMMAND_RPC_GETBANS::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL);
bool on_banned(const COMMAND_RPC_BANNED::request& req, COMMAND_RPC_BANNED::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL);
bool on_flush_txpool(const COMMAND_RPC_FLUSH_TRANSACTION_POOL::request& req, COMMAND_RPC_FLUSH_TRANSACTION_POOL::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL); bool on_flush_txpool(const COMMAND_RPC_FLUSH_TRANSACTION_POOL::request& req, COMMAND_RPC_FLUSH_TRANSACTION_POOL::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL);
bool on_get_output_histogram(const COMMAND_RPC_GET_OUTPUT_HISTOGRAM::request& req, COMMAND_RPC_GET_OUTPUT_HISTOGRAM::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL); bool on_get_output_histogram(const COMMAND_RPC_GET_OUTPUT_HISTOGRAM::request& req, COMMAND_RPC_GET_OUTPUT_HISTOGRAM::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL);
bool on_get_version(const COMMAND_RPC_GET_VERSION::request& req, COMMAND_RPC_GET_VERSION::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL); bool on_get_version(const COMMAND_RPC_GET_VERSION::request& req, COMMAND_RPC_GET_VERSION::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL);

View file

@ -84,7 +84,7 @@ namespace cryptonote
// advance which version they will stop working with // advance which version they will stop working with
// Don't go over 32767 for any of these // Don't go over 32767 for any of these
#define CORE_RPC_VERSION_MAJOR 2 #define CORE_RPC_VERSION_MAJOR 2
#define CORE_RPC_VERSION_MINOR 6 #define CORE_RPC_VERSION_MINOR 7
#define MAKE_CORE_RPC_VERSION(major,minor) (((major)<<16)|(minor)) #define MAKE_CORE_RPC_VERSION(major,minor) (((major)<<16)|(minor))
#define CORE_RPC_VERSION MAKE_CORE_RPC_VERSION(CORE_RPC_VERSION_MAJOR, CORE_RPC_VERSION_MINOR) #define CORE_RPC_VERSION MAKE_CORE_RPC_VERSION(CORE_RPC_VERSION_MAJOR, CORE_RPC_VERSION_MINOR)
@ -1876,6 +1876,33 @@ namespace cryptonote
typedef epee::misc_utils::struct_init<response_t> response; typedef epee::misc_utils::struct_init<response_t> response;
}; };
struct COMMAND_RPC_BANNED
{
struct request_t
{
std::string address;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(address)
END_KV_SERIALIZE_MAP()
};
typedef epee::misc_utils::struct_init<request_t> request;
struct response_t
{
std::string status;
bool banned;
uint32_t seconds;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(status)
KV_SERIALIZE(banned)
KV_SERIALIZE(seconds)
END_KV_SERIALIZE_MAP()
};
typedef epee::misc_utils::struct_init<response_t> response;
};
struct COMMAND_RPC_FLUSH_TRANSACTION_POOL struct COMMAND_RPC_FLUSH_TRANSACTION_POOL
{ {
struct request_t struct request_t

View file

@ -36,6 +36,7 @@
#include "cryptonote_protocol/cryptonote_protocol_handler.inl" #include "cryptonote_protocol/cryptonote_protocol_handler.inl"
#define MAKE_IPV4_ADDRESS(a,b,c,d) epee::net_utils::ipv4_network_address{MAKE_IP(a,b,c,d),0} #define MAKE_IPV4_ADDRESS(a,b,c,d) epee::net_utils::ipv4_network_address{MAKE_IP(a,b,c,d),0}
#define MAKE_IPV4_SUBNET(a,b,c,d,e) epee::net_utils::ipv4_network_subnet{MAKE_IP(a,b,c,d),e}
namespace cryptonote { namespace cryptonote {
class blockchain_storage; class blockchain_storage;
@ -93,11 +94,10 @@ typedef nodetool::node_server<cryptonote::t_cryptonote_protocol_handler<test_cor
static bool is_blocked(Server &server, const epee::net_utils::network_address &address, time_t *t = NULL) static bool is_blocked(Server &server, const epee::net_utils::network_address &address, time_t *t = NULL)
{ {
const std::string host = address.host_str(); std::map<epee::net_utils::network_address, time_t> hosts = server.get_blocked_hosts();
std::map<std::string, time_t> hosts = server.get_blocked_hosts();
for (auto rec: hosts) for (auto rec: hosts)
{ {
if (rec.first == host) if (rec.first == address)
{ {
if (t) if (t)
*t = rec.second; *t = rec.second;
@ -208,5 +208,37 @@ TEST(ban, limit)
ASSERT_TRUE(is_blocked(server,MAKE_IPV4_ADDRESS(1,2,3,4))); ASSERT_TRUE(is_blocked(server,MAKE_IPV4_ADDRESS(1,2,3,4)));
} }
TEST(ban, subnet)
{
time_t seconds;
test_core pr_core;
cryptonote::t_cryptonote_protocol_handler<test_core> cprotocol(pr_core, NULL);
Server server(cprotocol);
cprotocol.set_p2p_endpoint(&server);
ASSERT_TRUE(server.block_subnet(MAKE_IPV4_SUBNET(1,2,3,4,24), 10));
ASSERT_TRUE(server.get_blocked_subnets().size() == 1);
ASSERT_TRUE(server.is_host_blocked(MAKE_IPV4_ADDRESS(1,2,3,4), &seconds));
ASSERT_TRUE(seconds >= 9);
ASSERT_TRUE(server.is_host_blocked(MAKE_IPV4_ADDRESS(1,2,3,255), &seconds));
ASSERT_TRUE(server.is_host_blocked(MAKE_IPV4_ADDRESS(1,2,3,0), &seconds));
ASSERT_FALSE(server.is_host_blocked(MAKE_IPV4_ADDRESS(1,2,4,0), &seconds));
ASSERT_FALSE(server.is_host_blocked(MAKE_IPV4_ADDRESS(1,2,2,0), &seconds));
ASSERT_TRUE(server.unblock_subnet(MAKE_IPV4_SUBNET(1,2,3,8,24)));
ASSERT_TRUE(server.get_blocked_subnets().size() == 0);
ASSERT_FALSE(server.is_host_blocked(MAKE_IPV4_ADDRESS(1,2,3,255), &seconds));
ASSERT_FALSE(server.is_host_blocked(MAKE_IPV4_ADDRESS(1,2,3,0), &seconds));
ASSERT_TRUE(server.block_subnet(MAKE_IPV4_SUBNET(1,2,3,4,8), 10));
ASSERT_TRUE(server.get_blocked_subnets().size() == 1);
ASSERT_TRUE(server.is_host_blocked(MAKE_IPV4_ADDRESS(1,255,3,255), &seconds));
ASSERT_TRUE(server.is_host_blocked(MAKE_IPV4_ADDRESS(1,0,3,255), &seconds));
ASSERT_FALSE(server.unblock_subnet(MAKE_IPV4_SUBNET(1,2,3,8,24)));
ASSERT_TRUE(server.get_blocked_subnets().size() == 1);
ASSERT_TRUE(server.block_subnet(MAKE_IPV4_SUBNET(1,2,3,4,8), 10));
ASSERT_TRUE(server.get_blocked_subnets().size() == 1);
ASSERT_TRUE(server.unblock_subnet(MAKE_IPV4_SUBNET(1,255,0,0,8)));
ASSERT_TRUE(server.get_blocked_subnets().size() == 0);
}
namespace nodetool { template class node_server<cryptonote::t_cryptonote_protocol_handler<test_core>>; } namespace nodetool { template class node_server<cryptonote::t_cryptonote_protocol_handler<test_core>>; }
namespace cryptonote { template class t_cryptonote_protocol_handler<test_core>; } namespace cryptonote { template class t_cryptonote_protocol_handler<test_core>; }

View file

@ -524,6 +524,24 @@ TEST(get_network_address, ipv4)
EXPECT_STREQ("23.0.0.254:2000", address->str().c_str()); EXPECT_STREQ("23.0.0.254:2000", address->str().c_str());
} }
TEST(get_network_address, ipv4subnet)
{
expect<epee::net_utils::ipv4_network_subnet> address = net::get_ipv4_subnet_address("0.0.0.0", true);
EXPECT_STREQ("0.0.0.0/32", address->str().c_str());
address = net::get_ipv4_subnet_address("0.0.0.0");
EXPECT_TRUE(!address);
address = net::get_ipv4_subnet_address("0.0.0.0/32");
EXPECT_STREQ("0.0.0.0/32", address->str().c_str());
address = net::get_ipv4_subnet_address("0.0.0.0/0");
EXPECT_STREQ("0.0.0.0/0", address->str().c_str());
address = net::get_ipv4_subnet_address("12.34.56.78/16");
EXPECT_STREQ("12.34.0.0/16", address->str().c_str());
}
namespace namespace
{ {
using stream_type = boost::asio::ip::tcp; using stream_type = boost::asio::ip::tcp;