banman.cpp raw

   1  // Copyright (c) 2009-2010 Satoshi Nakamoto
   2  // Copyright (c) 2009-present The Bitcoin Core developers
   3  // Distributed under the MIT software license, see the accompanying
   4  // file COPYING or http://www.opensource.org/licenses/mit-license.php.
   5  
   6  #include <banman.h>
   7  
   8  #include <common/system.h>
   9  #include <netaddress.h>
  10  #include <node/interface_ui.h>
  11  #include <sync.h>
  12  #include <util/log.h>
  13  #include <util/time.h>
  14  #include <util/translation.h>
  15  
  16  
  17  BanMan::BanMan(fs::path ban_file, CClientUIInterface* client_interface, int64_t default_ban_time)
  18      : m_client_interface(client_interface), m_ban_db(std::move(ban_file)), m_default_ban_time(default_ban_time)
  19  {
  20      LoadBanlist();
  21      DumpBanlist();
  22  }
  23  
  24  BanMan::~BanMan()
  25  {
  26      DumpBanlist();
  27  }
  28  
  29  void BanMan::LoadBanlist()
  30  {
  31      LOCK(m_banned_mutex);
  32  
  33      if (m_client_interface) m_client_interface->InitMessage(_("Loading banlist…"));
  34  
  35      const auto start{SteadyClock::now()};
  36      if (m_ban_db.Read(m_banned)) {
  37          SweepBanned(); // sweep out unused entries
  38  
  39          LogDebug(BCLog::NET, "Loaded %d banned node addresses/subnets %dms", m_banned.size(),
  40                   Ticks<std::chrono::milliseconds>(SteadyClock::now() - start));
  41      } else {
  42          LogInfo("Recreating the banlist database");
  43          m_banned = {};
  44          m_is_dirty = true;
  45      }
  46  }
  47  
  48  void BanMan::DumpBanlist()
  49  {
  50      static Mutex dump_mutex;
  51      LOCK(dump_mutex);
  52  
  53      banmap_t banmap;
  54      {
  55          LOCK(m_banned_mutex);
  56          SweepBanned();
  57          if (!m_is_dirty) return;
  58          banmap = m_banned;
  59          m_is_dirty = false;
  60      }
  61  
  62      const auto start{SteadyClock::now()};
  63      if (!m_ban_db.Write(banmap)) {
  64          LOCK(m_banned_mutex);
  65          m_is_dirty = true;
  66      }
  67  
  68      LogDebug(BCLog::NET, "Flushed %d banned node addresses/subnets to disk %dms", banmap.size(),
  69               Ticks<std::chrono::milliseconds>(SteadyClock::now() - start));
  70  }
  71  
  72  void BanMan::ClearBanned()
  73  {
  74      {
  75          LOCK(m_banned_mutex);
  76          m_banned.clear();
  77          m_is_dirty = true;
  78      }
  79      DumpBanlist(); //store banlist to disk
  80      if (m_client_interface) m_client_interface->BannedListChanged();
  81  }
  82  
  83  bool BanMan::IsDiscouraged(const CNetAddr& net_addr)
  84  {
  85      LOCK(m_banned_mutex);
  86      return m_discouraged.contains(net_addr.GetAddrBytes());
  87  }
  88  
  89  bool BanMan::IsBanned(const CNetAddr& net_addr)
  90  {
  91      auto current_time = GetTime();
  92      LOCK(m_banned_mutex);
  93      for (const auto& it : m_banned) {
  94          CSubNet sub_net = it.first;
  95          CBanEntry ban_entry = it.second;
  96  
  97          if (current_time < ban_entry.nBanUntil && sub_net.Match(net_addr)) {
  98              return true;
  99          }
 100      }
 101      return false;
 102  }
 103  
 104  bool BanMan::IsBanned(const CSubNet& sub_net)
 105  {
 106      auto current_time = GetTime();
 107      LOCK(m_banned_mutex);
 108      banmap_t::iterator i = m_banned.find(sub_net);
 109      if (i != m_banned.end()) {
 110          CBanEntry ban_entry = (*i).second;
 111          if (current_time < ban_entry.nBanUntil) {
 112              return true;
 113          }
 114      }
 115      return false;
 116  }
 117  
 118  void BanMan::Ban(const CNetAddr& net_addr, int64_t ban_time_offset, bool since_unix_epoch)
 119  {
 120      CSubNet sub_net(net_addr);
 121      Ban(sub_net, ban_time_offset, since_unix_epoch);
 122  }
 123  
 124  void BanMan::Discourage(const CNetAddr& net_addr)
 125  {
 126      LOCK(m_banned_mutex);
 127      m_discouraged.insert(net_addr.GetAddrBytes());
 128  }
 129  
 130  void BanMan::Ban(const CSubNet& sub_net, int64_t ban_time_offset, bool since_unix_epoch)
 131  {
 132      CBanEntry ban_entry(GetTime());
 133  
 134      int64_t normalized_ban_time_offset = ban_time_offset;
 135      bool normalized_since_unix_epoch = since_unix_epoch;
 136      if (ban_time_offset <= 0) {
 137          normalized_ban_time_offset = m_default_ban_time;
 138          normalized_since_unix_epoch = false;
 139      }
 140      ban_entry.nBanUntil = (normalized_since_unix_epoch ? 0 : GetTime()) + normalized_ban_time_offset;
 141  
 142      {
 143          LOCK(m_banned_mutex);
 144          if (m_banned[sub_net].nBanUntil < ban_entry.nBanUntil) {
 145              m_banned[sub_net] = ban_entry;
 146              m_is_dirty = true;
 147          } else
 148              return;
 149      }
 150      if (m_client_interface) m_client_interface->BannedListChanged();
 151  
 152      //store banlist to disk immediately
 153      DumpBanlist();
 154  }
 155  
 156  bool BanMan::Unban(const CNetAddr& net_addr)
 157  {
 158      CSubNet sub_net(net_addr);
 159      return Unban(sub_net);
 160  }
 161  
 162  bool BanMan::Unban(const CSubNet& sub_net)
 163  {
 164      {
 165          LOCK(m_banned_mutex);
 166          if (m_banned.erase(sub_net) == 0) return false;
 167          m_is_dirty = true;
 168      }
 169      if (m_client_interface) m_client_interface->BannedListChanged();
 170      DumpBanlist(); //store banlist to disk immediately
 171      return true;
 172  }
 173  
 174  void BanMan::GetBanned(banmap_t& banmap)
 175  {
 176      LOCK(m_banned_mutex);
 177      // Sweep the banlist so expired bans are not returned
 178      SweepBanned();
 179      banmap = m_banned; //create a thread safe copy
 180  }
 181  
 182  void BanMan::SweepBanned()
 183  {
 184      AssertLockHeld(m_banned_mutex);
 185  
 186      int64_t now = GetTime();
 187      bool notify_ui = false;
 188      banmap_t::iterator it = m_banned.begin();
 189      while (it != m_banned.end()) {
 190          CSubNet sub_net = (*it).first;
 191          CBanEntry ban_entry = (*it).second;
 192          if (!sub_net.IsValid() || now > ban_entry.nBanUntil) {
 193              m_banned.erase(it++);
 194              m_is_dirty = true;
 195              notify_ui = true;
 196              LogDebug(BCLog::NET, "Removed banned node address/subnet: %s\n", sub_net.ToString());
 197          } else {
 198              ++it;
 199          }
 200      }
 201  
 202      // update UI
 203      if (notify_ui && m_client_interface) {
 204          m_client_interface->BannedListChanged();
 205      }
 206  }
 207