banman.cpp raw

   1  // Copyright (c) 2009-2010 Satoshi Nakamoto
   2  // Copyright (c) 2009-2022 The Limenka 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 <logging.h>
  10  #include <netaddress.h>
  11  #include <node/interface_ui.h>
  12  #include <scheduler.h>
  13  #include <sync.h>
  14  #include <util/time.h>
  15  #include <util/translation.h>
  16  
  17  #include <algorithm>
  18  #include <limits>
  19  
  20  
  21  BanMan::BanMan(fs::path ban_file, CClientUIInterface* client_interface, int64_t default_ban_time)
  22      : m_client_interface(client_interface), m_ban_db(std::move(ban_file)), m_default_ban_time(default_ban_time)
  23  {
  24      LoadBanlist();
  25      DumpBanlist();
  26  }
  27  
  28  BanMan::~BanMan()
  29  {
  30      DumpBanlist();
  31  }
  32  
  33  void BanMan::SetScheduler(CScheduler& scheduler)
  34  {
  35      LOCK(m_banned_mutex);
  36      m_scheduler = &scheduler;
  37  }
  38  
  39  void BanMan::EnsureSweepScheduled()
  40  {
  41      LOCK(m_banned_mutex);
  42      if (!m_scheduler) return;
  43      if (m_sweep_started) return;
  44      m_sweep_started = true;
  45      SweepBannedAndSchedule();
  46  }
  47  
  48  void BanMan::SweepBannedAndSchedule()
  49  {
  50      SweepBanned();
  51      ScheduleNextSweep();
  52  }
  53  
  54  void BanMan::SweepBannedAndSchedule(uint64_t expected_seq)
  55  {
  56      LOCK(m_banned_mutex);
  57      if (expected_seq != m_sweep_seq) return;
  58      SweepBannedAndSchedule();
  59  }
  60  
  61  void BanMan::ScheduleNextSweep()
  62  {
  63      AssertLockHeld(m_banned_mutex);
  64  
  65      m_next_sweep_time = std::numeric_limits<int64_t>::max();
  66      if (!m_scheduler) return;
  67      if (m_banned.empty()) return;
  68  
  69      int64_t earliest = std::numeric_limits<int64_t>::max();
  70      for (const auto& [subnet, entry] : m_banned) {
  71          if (entry.nBanUntil < earliest) {
  72              earliest = entry.nBanUntil;
  73          }
  74      }
  75  
  76      m_next_sweep_time = earliest;
  77      uint64_t seq = ++m_sweep_seq;
  78      int64_t now = GetTime();
  79      auto delay = std::chrono::seconds(std::max<int64_t>(0, earliest - now));
  80      m_scheduler->scheduleFromNow([this, seq] { SweepBannedAndSchedule(seq); }, delay);
  81  }
  82  
  83  void BanMan::LoadBanlist()
  84  {
  85      LOCK(m_banned_mutex);
  86  
  87      if (m_client_interface) m_client_interface->InitMessage(_("Loading banlist…"));
  88  
  89      const auto start{SteadyClock::now()};
  90      if (m_ban_db.Read(m_banned)) {
  91          SweepBanned(); // sweep out unused entries
  92  
  93          LogDebug(BCLog::NET, "Loaded %d banned node addresses/subnets  %dms\n", m_banned.size(),
  94                   Ticks<std::chrono::milliseconds>(SteadyClock::now() - start));
  95      } else {
  96          LogPrintf("Recreating the banlist database\n");
  97          m_banned = {};
  98          m_is_dirty = true;
  99      }
 100  }
 101  
 102  void BanMan::DumpBanlist()
 103  {
 104      static Mutex dump_mutex;
 105      LOCK(dump_mutex);
 106  
 107      banmap_t banmap;
 108      {
 109          LOCK(m_banned_mutex);
 110          SweepBanned();
 111          if (!m_is_dirty) return;
 112          banmap = m_banned;
 113          m_is_dirty = false;
 114      }
 115  
 116      const auto start{SteadyClock::now()};
 117      if (!m_ban_db.Write(banmap)) {
 118          LOCK(m_banned_mutex);
 119          m_is_dirty = true;
 120      }
 121  
 122      LogDebug(BCLog::NET, "Flushed %d banned node addresses/subnets to disk  %dms\n", banmap.size(),
 123               Ticks<std::chrono::milliseconds>(SteadyClock::now() - start));
 124  }
 125  
 126  void BanMan::ClearBanned()
 127  {
 128      {
 129          LOCK(m_banned_mutex);
 130          m_banned.clear();
 131          m_is_dirty = true;
 132      }
 133      DumpBanlist(); //store banlist to disk
 134      if (m_client_interface) m_client_interface->BannedListChanged();
 135  }
 136  
 137  bool BanMan::IsDiscouraged(const CNetAddr& net_addr)
 138  {
 139      LOCK(m_banned_mutex);
 140      return m_discouraged.contains(net_addr.GetAddrBytes());
 141  }
 142  
 143  bool BanMan::IsBanned(const CNetAddr& net_addr)
 144  {
 145      auto current_time = GetTime();
 146      LOCK(m_banned_mutex);
 147      for (const auto& it : m_banned) {
 148          CSubNet sub_net = it.first;
 149          CBanEntry ban_entry = it.second;
 150  
 151          if (current_time < ban_entry.nBanUntil && sub_net.Match(net_addr)) {
 152              return true;
 153          }
 154      }
 155      return false;
 156  }
 157  
 158  bool BanMan::IsBanned(const CSubNet& sub_net)
 159  {
 160      auto current_time = GetTime();
 161      LOCK(m_banned_mutex);
 162      banmap_t::iterator i = m_banned.find(sub_net);
 163      if (i != m_banned.end()) {
 164          CBanEntry ban_entry = (*i).second;
 165          if (current_time < ban_entry.nBanUntil) {
 166              return true;
 167          }
 168      }
 169      return false;
 170  }
 171  
 172  void BanMan::Ban(const CNetAddr& net_addr, int64_t ban_time_offset, bool since_unix_epoch)
 173  {
 174      CSubNet sub_net(net_addr);
 175      Ban(sub_net, ban_time_offset, since_unix_epoch);
 176  }
 177  
 178  void BanMan::Discourage(const CNetAddr& net_addr)
 179  {
 180      LOCK(m_banned_mutex);
 181      m_discouraged.insert(net_addr.GetAddrBytes());
 182  }
 183  
 184  void BanMan::Ban(const CSubNet& sub_net, int64_t ban_time_offset, bool since_unix_epoch)
 185  {
 186      CBanEntry ban_entry(GetTime());
 187  
 188      int64_t normalized_ban_time_offset = ban_time_offset;
 189      bool normalized_since_unix_epoch = since_unix_epoch;
 190      if (ban_time_offset <= 0) {
 191          normalized_ban_time_offset = m_default_ban_time;
 192          normalized_since_unix_epoch = false;
 193      }
 194      ban_entry.nBanUntil = (normalized_since_unix_epoch ? 0 : GetTime()) + normalized_ban_time_offset;
 195  
 196      {
 197          LOCK(m_banned_mutex);
 198          if (m_banned[sub_net].nBanUntil < ban_entry.nBanUntil) {
 199              m_banned[sub_net] = ban_entry;
 200              m_is_dirty = true;
 201              if (m_sweep_started && ban_entry.nBanUntil < m_next_sweep_time) {
 202                  m_next_sweep_time = ban_entry.nBanUntil;
 203                  uint64_t seq = ++m_sweep_seq;
 204                  int64_t now = GetTime();
 205                  auto delay = std::chrono::seconds(std::max<int64_t>(0, m_next_sweep_time - now));
 206                  m_scheduler->scheduleFromNow([this, seq] { SweepBannedAndSchedule(seq); }, delay);
 207              }
 208          } else
 209              return;
 210      }
 211      if (m_client_interface) m_client_interface->BannedListChanged();
 212  
 213      //store banlist to disk immediately
 214      DumpBanlist();
 215  }
 216  
 217  bool BanMan::Unban(const CNetAddr& net_addr)
 218  {
 219      CSubNet sub_net(net_addr);
 220      return Unban(sub_net);
 221  }
 222  
 223  bool BanMan::Unban(const CSubNet& sub_net)
 224  {
 225      {
 226          LOCK(m_banned_mutex);
 227          if (m_banned.erase(sub_net) == 0) return false;
 228          m_is_dirty = true;
 229      }
 230      if (m_client_interface) m_client_interface->BannedListChanged();
 231      DumpBanlist(); //store banlist to disk immediately
 232      return true;
 233  }
 234  
 235  void BanMan::GetBanned(banmap_t& banmap)
 236  {
 237      LOCK(m_banned_mutex);
 238      // Sweep the banlist so expired bans are not returned
 239      SweepBanned();
 240      banmap = m_banned; //create a thread safe copy
 241  }
 242  
 243  void BanMan::SweepBanned()
 244  {
 245      AssertLockHeld(m_banned_mutex);
 246  
 247      int64_t now = GetTime();
 248      bool notify_ui = false;
 249      banmap_t::iterator it = m_banned.begin();
 250      while (it != m_banned.end()) {
 251          CSubNet sub_net = (*it).first;
 252          CBanEntry ban_entry = (*it).second;
 253          if (!sub_net.IsValid() || now >= ban_entry.nBanUntil) {
 254              m_banned.erase(it++);
 255              m_is_dirty = true;
 256              notify_ui = true;
 257              LogDebug(BCLog::NET, "Removed banned node address/subnet: %s\n", sub_net.ToString());
 258          } else {
 259              ++it;
 260          }
 261      }
 262  
 263      // update UI
 264      if (notify_ui && m_client_interface) {
 265          m_client_interface->BannedListChanged();
 266      }
 267  }
 268