-
Notifications
You must be signed in to change notification settings - Fork 287
/
banmanager.go
258 lines (215 loc) · 6.07 KB
/
banmanager.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
// Copyright (c) 2021-2023 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package banmanager
import (
"fmt"
"net"
"runtime/debug"
"sync"
"time"
"github.com/decred/dcrd/connmgr/v3"
"github.com/decred/dcrd/peer/v3"
)
// Config is the configuration struct for the ban manager.
type Config struct {
// DisableBanning represents the status of disabling banning of
// misbehaving peers.
DisableBanning bool
// BanThreshold represents the maximum allowed ban score before
// misbehaving peers are disconnecting and banned.
BanThreshold uint32
// BanDuration is the duration for which misbehaving peers stay banned for.
BanDuration time.Duration
// MaxPeers indicates the maximum number of inbound and outbound
// peers allowed.
MaxPeers int
// Whitelist represents the whitelisted IPs of the server.
WhiteList []net.IPNet
}
// banMgrPeer extends a peer to maintain additional state maintained by the
// ban manager.
type banMgrPeer struct {
*peer.Peer
isWhitelisted bool
banScore connmgr.DynamicBanScore
}
// BanManager represents a peer ban score tracking manager.
type BanManager struct {
cfg Config
peers map[*peer.Peer]*banMgrPeer
banned map[string]time.Time
mtx sync.Mutex
}
// NewBanManager initializes a new peer banning manager.
func NewBanManager(cfg *Config) *BanManager {
return &BanManager{
cfg: *cfg,
peers: make(map[*peer.Peer]*banMgrPeer, cfg.MaxPeers),
banned: make(map[string]time.Time, cfg.MaxPeers),
}
}
// lookupPeer returns the ban manager peer that maintains additional state for
// a given base peer. In the event the mapping does not exist, a warning is
// logged and nil is returned.
//
// This function MUST be called with the ban manager mutex locked (for reads).
func (bm *BanManager) lookupPeer(p *peer.Peer) *banMgrPeer {
bmp, ok := bm.peers[p]
if !ok {
log.Warnf("Attempt to lookup unknown peer %s\nStack: %v", p,
string(debug.Stack()))
return nil
}
return bmp
}
// isPeerWhitelisted checks if the provided peer is whitelisted per the
// provided whitelist.
func (bm *BanManager) isPeerWhitelisted(p *peer.Peer, whitelist []net.IPNet) bool {
host, _, err := net.SplitHostPort(p.Addr())
if err != nil {
log.Errorf("Unable to split peer '%s' IP: %v", p.Addr(), err)
return false
}
ip := net.ParseIP(host)
if ip == nil {
log.Errorf("Unable to parse IP '%s'", p.Addr())
return false
}
for _, ipnet := range whitelist {
if ipnet.Contains(ip) {
return true
}
}
return false
}
// IsPeerWhitelisted checks if the provided peer is whitelisted.
func (bm *BanManager) IsPeerWhitelisted(p *peer.Peer) bool {
bm.mtx.Lock()
bmp := bm.lookupPeer(p)
bm.mtx.Unlock()
if bmp == nil {
return false
}
return bmp.isWhitelisted
}
// AddPeer adds the provided peer to the ban manager.
func (bm *BanManager) AddPeer(p *peer.Peer) error {
host, _, err := net.SplitHostPort(p.Addr())
if err != nil {
p.Disconnect()
return fmt.Errorf("cannot split hostport %w", err)
}
bm.mtx.Lock()
banEnd, ok := bm.banned[host]
bm.mtx.Unlock()
if ok {
if time.Now().Before(banEnd) {
p.Disconnect()
return fmt.Errorf("peer %s is banned for another %v - disconnecting",
host, time.Until(banEnd))
}
log.Infof("Peer %s is no longer banned", host)
bm.mtx.Lock()
delete(bm.banned, host)
bm.mtx.Unlock()
}
bmp := &banMgrPeer{
Peer: p,
isWhitelisted: bm.isPeerWhitelisted(p, bm.cfg.WhiteList),
}
bm.mtx.Lock()
bm.peers[p] = bmp
bm.mtx.Unlock()
return nil
}
// RemovePeer discards the provided peer from the ban manager.
func (bm *BanManager) RemovePeer(p *peer.Peer) {
bm.mtx.Lock()
delete(bm.peers, p)
bm.mtx.Unlock()
}
// BanPeer bans the provided peer.
func (bm *BanManager) BanPeer(p *peer.Peer) {
// Return immediately if banning is disabled.
if bm.cfg.DisableBanning {
return
}
bm.mtx.Lock()
bmp := bm.lookupPeer(p)
bm.mtx.Unlock()
if bmp == nil {
return
}
// Return if the peer is whitelisted.
if bmp.isWhitelisted {
return
}
// Ban and remove the peer.
host, _, err := net.SplitHostPort(p.Addr())
if err != nil {
log.Debugf("can't split ban peer %s %v", p.Addr(), err)
return
}
direction := directionString(p.Inbound())
log.Infof("Banned peer %s (%s) for %v", host, direction,
bm.cfg.BanDuration)
bm.mtx.Lock()
bm.banned[host] = time.Now().Add(bm.cfg.BanDuration)
bm.mtx.Unlock()
p.Disconnect()
bm.RemovePeer(p)
}
// AddBanScore increases the persistent and decaying ban scores of the
// provided peer by the values passed as parameters. If the resulting score
// exceeds half of the ban threshold, a warning is logged including the reason
// provided. Further, if the score is above the ban threshold, the peer will
// be banned.
func (bm *BanManager) AddBanScore(p *peer.Peer, persistent, transient uint32, reason string) bool {
// No warning is logged and no score is calculated if banning is disabled.
if bm.cfg.DisableBanning {
return false
}
bm.mtx.Lock()
bmp := bm.lookupPeer(p)
bm.mtx.Unlock()
if bmp == nil {
return false
}
if bmp.isWhitelisted {
log.Debugf("Misbehaving whitelisted peer %s: %s", p, reason)
return false
}
banScore := bmp.banScore.Int()
warnThreshold := bm.cfg.BanThreshold >> 1
if transient == 0 && persistent == 0 {
// The score is not being increased, but a warning message is still
// logged if the score is above the warn threshold.
if banScore > warnThreshold {
log.Warnf("Misbehaving peer %s: %s -- ban score is %d, "+
"it was not increased this time", p, reason, banScore)
}
return false
}
banScore = bmp.banScore.Increase(persistent, transient)
if banScore > warnThreshold {
log.Warnf("Misbehaving peer %s: %s -- ban score increased to %d",
p, reason, banScore)
if banScore > bm.cfg.BanThreshold {
log.Warnf("Misbehaving peer %s -- banning and disconnecting", p)
bm.BanPeer(p)
return true
}
}
return false
}
// BanScore returns the ban score of the provided peer.
func (bm *BanManager) BanScore(p *peer.Peer) uint32 {
bm.mtx.Lock()
bmp := bm.lookupPeer(p)
bm.mtx.Unlock()
if bmp == nil {
return 0
}
return bmp.banScore.Int()
}