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