aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormoneromooo-monero <moneromooo-monero@users.noreply.github.com>2019-03-29 10:47:53 +0000
committermoneromooo-monero <moneromooo-monero@users.noreply.github.com>2019-07-16 11:35:53 +0000
commit65c40049633f0c5db3c24af8716bb683520f368c (patch)
tree9531d5d4c2cfa4e5a5b1ac35cea13af1b173ccbe
parentp2p: store network address directly in blocked host list (diff)
downloadmonero-65c40049633f0c5db3c24af8716bb683520f368c.tar.xz
allow blocking whole subnets
-rw-r--r--contrib/epee/include/net/abstract_tcp_server2.h2
-rw-r--r--contrib/epee/include/net/net_utils_base.h49
-rw-r--r--contrib/epee/src/net_utils_base.cpp18
-rw-r--r--src/daemon/command_parser_executor.cpp7
-rw-r--r--src/daemon/command_parser_executor.h2
-rw-r--r--src/daemon/command_server.cpp8
-rw-r--r--src/daemon/rpc_command_executor.cpp53
-rw-r--r--src/daemon/rpc_command_executor.h6
-rw-r--r--src/net/error.h3
-rw-r--r--src/net/parse.cpp23
-rw-r--r--src/net/parse.h13
-rw-r--r--src/p2p/net_node.h9
-rw-r--r--src/p2p/net_node.inl104
-rw-r--r--src/p2p/net_node_common.h5
-rw-r--r--src/rpc/core_rpc_server.cpp58
-rw-r--r--src/rpc/core_rpc_server.h2
-rw-r--r--src/rpc/core_rpc_server_commands_defs.h29
-rw-r--r--tests/unit_tests/ban.cpp38
-rw-r--r--tests/unit_tests/net.cpp18
19 files changed, 413 insertions, 34 deletions
diff --git a/contrib/epee/include/net/abstract_tcp_server2.h b/contrib/epee/include/net/abstract_tcp_server2.h
index 374a28a2e..c1aa0fe5f 100644
--- a/contrib/epee/include/net/abstract_tcp_server2.h
+++ b/contrib/epee/include/net/abstract_tcp_server2.h
@@ -70,7 +70,7 @@ namespace net_utils
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:
virtual ~i_connection_filter(){}
};
diff --git a/contrib/epee/include/net/net_utils_base.h b/contrib/epee/include/net/net_utils_base.h
index 83e6b5ab8..62618537f 100644
--- a/contrib/epee/include/net/net_utils_base.h
+++ b/contrib/epee/include/net/net_utils_base.h
@@ -41,7 +41,7 @@
#define MONERO_DEFAULT_LOG_CATEGORY "net"
#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
#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
{ 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
{
struct interface
diff --git a/contrib/epee/src/net_utils_base.cpp b/contrib/epee/src/net_utils_base.cpp
index 9b781027e..b7f07a23b 100644
--- a/contrib/epee/src/net_utils_base.cpp
+++ b/contrib/epee/src/net_utils_base.cpp
@@ -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_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
{
// clang typeid workaround
diff --git a/src/daemon/command_parser_executor.cpp b/src/daemon/command_parser_executor.cpp
index 0b452800e..ed799096b 100644
--- a/src/daemon/command_parser_executor.cpp
+++ b/src/daemon/command_parser_executor.cpp
@@ -596,6 +596,13 @@ bool t_command_parser_executor::unban(const std::vector<std::string>& args)
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)
{
if (args.size() > 1) return false;
diff --git a/src/daemon/command_parser_executor.h b/src/daemon/command_parser_executor.h
index 2efd78ec0..aeaf4d254 100644
--- a/src/daemon/command_parser_executor.h
+++ b/src/daemon/command_parser_executor.h
@@ -127,6 +127,8 @@ public:
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 output_histogram(const std::vector<std::string>& args);
diff --git a/src/daemon/command_server.cpp b/src/daemon/command_server.cpp
index f665eec9c..f0b4b4ba0 100644
--- a/src/daemon/command_server.cpp
+++ b/src/daemon/command_server.cpp
@@ -243,10 +243,16 @@ t_command_server::t_command_server(
m_command_lookup.set_handler(
"unban"
, std::bind(&t_command_parser_executor::unban, &m_parser, p::_1)
- , "unban <IP>"
+ , "unban <address>"
, "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(
"flush_txpool"
, std::bind(&t_command_parser_executor::flush_txpool, &m_parser, p::_1)
, "flush_txpool [<txid>]"
diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp
index cca0f75f9..bf4b3b09e 100644
--- a/src/daemon/rpc_command_executor.cpp
+++ b/src/daemon/rpc_command_executor.cpp
@@ -1641,14 +1641,14 @@ bool t_rpc_command_executor::print_bans()
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;
}
-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::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;
cryptonote::COMMAND_RPC_SETBANS::ban ban;
- if (!epee::string_tools::get_ip_int32_from_string(ban.ip, ip))
- {
- tools::fail_msg_writer() << "Invalid IP";
- return true;
- }
+ ban.host = address;
+ ban.ip = 0;
ban.ban = true;
ban.seconds = seconds;
req.bans.push_back(ban);
@@ -1684,7 +1681,7 @@ bool t_rpc_command_executor::ban(const std::string &ip, time_t seconds)
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::response res;
@@ -1692,11 +1689,8 @@ bool t_rpc_command_executor::unban(const std::string &ip)
epee::json_rpc::error error_resp;
cryptonote::COMMAND_RPC_SETBANS::ban ban;
- if (!epee::string_tools::get_ip_int32_from_string(ban.ip, ip))
- {
- tools::fail_msg_writer() << "Invalid IP";
- return true;
- }
+ ban.host = address;
+ ban.ip = 0;
ban.ban = false;
ban.seconds = 0;
req.bans.push_back(ban);
@@ -1720,6 +1714,39 @@ bool t_rpc_command_executor::unban(const std::string &ip)
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)
{
cryptonote::COMMAND_RPC_FLUSH_TRANSACTION_POOL::request req;
diff --git a/src/daemon/rpc_command_executor.h b/src/daemon/rpc_command_executor.h
index df2894d09..61c25736d 100644
--- a/src/daemon/rpc_command_executor.h
+++ b/src/daemon/rpc_command_executor.h
@@ -137,9 +137,11 @@ public:
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);
diff --git a/src/net/error.h b/src/net/error.h
index c8338f7e2..7c852dd20 100644
--- a/src/net/error.h
+++ b/src/net/error.h
@@ -42,7 +42,8 @@ namespace net
invalid_i2p_address,
invalid_port, //!< Outside of 0-65535 range
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.
diff --git a/src/net/parse.cpp b/src/net/parse.cpp
index eaaadb67e..d93d7d352 100644
--- a/src/net/parse.cpp
+++ b/src/net/parse.cpp
@@ -58,4 +58,27 @@ namespace net
return {epee::net_utils::ipv4_network_address{ip, port}};
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}};
+ }
}
diff --git a/src/net/parse.h b/src/net/parse.h
index 5804c4128..9f0d66ea6 100644
--- a/src/net/parse.h
+++ b/src/net/parse.h
@@ -50,5 +50,18 @@ namespace net
*/
expect<epee::net_utils::network_address>
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);
}
diff --git a/src/p2p/net_node.h b/src/p2p/net_node.h
index 2709e49af..9ee5ce0de 100644
--- a/src/p2p/net_node.h
+++ b/src/p2p/net_node.h
@@ -248,7 +248,11 @@ namespace nodetool
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 unblock_host(const epee::net_utils::network_address &address);
+ 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 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 add_host_fail(const epee::net_utils::network_address &address);
//----------------- 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 handle_command_line(
@@ -461,8 +465,9 @@ namespace nodetool
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_blocked_hosts_lock;
+ epee::critical_section m_blocked_hosts_lock; // for both hosts and subnets
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;
std::map<std::string, uint64_t> m_host_fails_score;
diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl
index 37a3f393d..f8011ec0e 100644
--- a/src/p2p/net_node.inl
+++ b/src/p2p/net_node.inl
@@ -155,19 +155,55 @@ namespace nodetool
}
//-----------------------------------------------------------------------------------
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);
+
+ const time_t now = time(nullptr);
+
+ // look in the hosts list
auto it = m_blocked_hosts.find(address);
- if(it == m_blocked_hosts.end())
- return true;
- if(time(nullptr) >= it->second)
+ if (it != m_blocked_hosts.end())
{
- m_blocked_hosts.erase(it);
- MCLOG_CYAN(el::Level::Info, "global", "Host " << address.host_str() << " unblocked.");
- return true;
+ if (now >= it->second)
+ {
+ m_blocked_hosts.erase(it);
+ MCLOG_CYAN(el::Level::Info, "global", "Host " << address.host_str() << " unblocked.");
+ 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>
@@ -223,6 +259,58 @@ namespace nodetool
}
//-----------------------------------------------------------------------------------
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)
{
if(!address.is_blockable())
diff --git a/src/p2p/net_node_common.h b/src/p2p/net_node_common.h
index e5e746e5b..34d151f5f 100644
--- a/src/p2p/net_node_common.h
+++ b/src/p2p/net_node_common.h
@@ -57,6 +57,7 @@ namespace nodetool
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 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 void add_used_stripe_peer(const t_connection_context &context)=0;
virtual void remove_used_stripe_peer(const t_connection_context &context)=0;
@@ -116,6 +117,10 @@ namespace nodetool
{
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)
{
return true;
diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp
index 5fbdcde39..cfa1c49d8 100644
--- a/src/rpc/core_rpc_server.cpp
+++ b/src/rpc/core_rpc_server.cpp
@@ -1786,6 +1786,46 @@ namespace cryptonote
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;
return true;
@@ -1798,13 +1838,29 @@ namespace cryptonote
for (auto i = req.bans.begin(); i != req.bans.end(); ++i)
{
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())
{
auto na_parsed = net::get_network_address(i->host, 0);
if (!na_parsed)
{
error_resp.code = CORE_RPC_ERROR_CODE_WRONG_PARAM;
- error_resp.message = "Unsupported host type";
+ error_resp.message = "Unsupported host/subnet type";
return false;
}
na = std::move(*na_parsed);
diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h
index e4683bbe2..266661fb0 100644
--- a/src/rpc/core_rpc_server.h
+++ b/src/rpc/core_rpc_server.h
@@ -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_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("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("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)
@@ -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_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_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_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);
diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h
index cfe4bbf23..a78faf5aa 100644
--- a/src/rpc/core_rpc_server_commands_defs.h
+++ b/src/rpc/core_rpc_server_commands_defs.h
@@ -84,7 +84,7 @@ namespace cryptonote
// advance which version they will stop working with
// Don't go over 32767 for any of these
#define CORE_RPC_VERSION_MAJOR 2
-#define CORE_RPC_VERSION_MINOR 6
+#define CORE_RPC_VERSION_MINOR 7
#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)
@@ -1876,6 +1876,33 @@ namespace cryptonote
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 request_t
diff --git a/tests/unit_tests/ban.cpp b/tests/unit_tests/ban.cpp
index 0b267172f..17fba90c6 100644
--- a/tests/unit_tests/ban.cpp
+++ b/tests/unit_tests/ban.cpp
@@ -36,6 +36,7 @@
#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_SUBNET(a,b,c,d,e) epee::net_utils::ipv4_network_subnet{MAKE_IP(a,b,c,d),e}
namespace cryptonote {
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)
{
- const std::string host = address.host_str();
- std::map<std::string, time_t> hosts = server.get_blocked_hosts();
+ std::map<epee::net_utils::network_address, time_t> hosts = server.get_blocked_hosts();
for (auto rec: hosts)
{
- if (rec.first == host)
+ if (rec.first == address)
{
if (t)
*t = rec.second;
@@ -208,5 +208,37 @@ TEST(ban, limit)
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 cryptonote { template class t_cryptonote_protocol_handler<test_core>; }
diff --git a/tests/unit_tests/net.cpp b/tests/unit_tests/net.cpp
index 326e63db8..3acf75f3b 100644
--- a/tests/unit_tests/net.cpp
+++ b/tests/unit_tests/net.cpp
@@ -524,6 +524,24 @@ TEST(get_network_address, ipv4)
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
{
using stream_type = boost::asio::ip::tcp;