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