From 515ac2951d767bdb712c4997010045715902e6ab Mon Sep 17 00:00:00 2001
From: moneromooo-monero <moneromooo-monero@users.noreply.github.com>
Date: Thu, 28 Mar 2019 22:24:36 +0000
Subject: [PATCH 1/2] p2p: store network address directly in blocked host list

rather than their string representation
---
 src/p2p/net_node.h          | 4 ++--
 src/p2p/net_node.inl        | 6 +++---
 src/p2p/net_node_common.h   | 6 +++---
 src/rpc/core_rpc_server.cpp | 8 ++++----
 4 files changed, 12 insertions(+), 12 deletions(-)

diff --git a/src/p2p/net_node.h b/src/p2p/net_node.h
index 42bb3b061..2709e49af 100644
--- a/src/p2p/net_node.h
+++ b/src/p2p/net_node.h
@@ -248,7 +248,7 @@ 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 std::map<std::string, time_t> get_blocked_hosts() { CRITICAL_REGION_LOCAL(m_blocked_hosts_lock); return m_blocked_hosts; }
+    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 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);
@@ -462,7 +462,7 @@ namespace nodetool
     epee::critical_section m_conn_fails_cache_lock;
 
     epee::critical_section m_blocked_hosts_lock;
-    std::map<std::string, time_t> m_blocked_hosts;
+    std::map<epee::net_utils::network_address, time_t> m_blocked_hosts;
 
     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 a5800df5e..37a3f393d 100644
--- a/src/p2p/net_node.inl
+++ b/src/p2p/net_node.inl
@@ -158,7 +158,7 @@ namespace nodetool
   bool node_server<t_payload_net_handler>::is_remote_host_allowed(const epee::net_utils::network_address &address)
   {
     CRITICAL_REGION_LOCAL(m_blocked_hosts_lock);
-    auto it = m_blocked_hosts.find(address.host_str());
+    auto it = m_blocked_hosts.find(address);
     if(it == m_blocked_hosts.end())
       return true;
     if(time(nullptr) >= it->second)
@@ -184,7 +184,7 @@ namespace nodetool
       limit = std::numeric_limits<time_t>::max();
     else
       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
     // the zone related to the connection, but really make sure everything is
@@ -214,7 +214,7 @@ namespace nodetool
   bool node_server<t_payload_net_handler>::unblock_host(const epee::net_utils::network_address &address)
   {
     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())
       return false;
     m_blocked_hosts.erase(i);
diff --git a/src/p2p/net_node_common.h b/src/p2p/net_node_common.h
index 26451b333..e5e746e5b 100644
--- a/src/p2p/net_node_common.h
+++ b/src/p2p/net_node_common.h
@@ -56,7 +56,7 @@ namespace nodetool
     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 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 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;
@@ -112,9 +112,9 @@ namespace nodetool
     {
       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 bool add_host_fail(const epee::net_utils::network_address &address)
     {
diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp
index b6836e636..5fbdcde39 100644
--- a/src/rpc/core_rpc_server.cpp
+++ b/src/rpc/core_rpc_server.cpp
@@ -1772,15 +1772,15 @@ namespace cryptonote
     PERF_TIMER(on_get_bans);
 
     auto now = time(nullptr);
-    std::map<std::string, 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)
+    std::map<epee::net_utils::network_address, time_t> blocked_hosts = m_p2p.get_blocked_hosts();
+    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) {
         COMMAND_RPC_GETBANS::ban b;
-        b.host = i->first;
+        b.host = i->first.host_str();
         b.ip = 0;
         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.seconds = i->second - now;
         res.bans.push_back(b);

From 65c40049633f0c5db3c24af8716bb683520f368c Mon Sep 17 00:00:00 2001
From: moneromooo-monero <moneromooo-monero@users.noreply.github.com>
Date: Fri, 29 Mar 2019 10:47:53 +0000
Subject: [PATCH 2/2] allow blocking whole subnets

---
 .../epee/include/net/abstract_tcp_server2.h   |   2 +-
 contrib/epee/include/net/net_utils_base.h     |  49 ++++++++-
 contrib/epee/src/net_utils_base.cpp           |  18 +++
 src/daemon/command_parser_executor.cpp        |   7 ++
 src/daemon/command_parser_executor.h          |   2 +
 src/daemon/command_server.cpp                 |   8 +-
 src/daemon/rpc_command_executor.cpp           |  53 ++++++---
 src/daemon/rpc_command_executor.h             |   6 +-
 src/net/error.h                               |   3 +-
 src/net/parse.cpp                             |  23 ++++
 src/net/parse.h                               |  13 +++
 src/p2p/net_node.h                            |   9 +-
 src/p2p/net_node.inl                          | 104 ++++++++++++++++--
 src/p2p/net_node_common.h                     |   5 +
 src/rpc/core_rpc_server.cpp                   |  58 +++++++++-
 src/rpc/core_rpc_server.h                     |   2 +
 src/rpc/core_rpc_server_commands_defs.h       |  29 ++++-
 tests/unit_tests/ban.cpp                      |  38 ++++++-
 tests/unit_tests/net.cpp                      |  18 +++
 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,9 +243,15 @@ 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)
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;