github.com/btcsuite/btcd@v0.24.0/connmgr/dynamicbanscore.go (about) 1 // Copyright (c) 2016 The btcsuite developers 2 // Use of this source code is governed by an ISC 3 // license that can be found in the LICENSE file. 4 5 package connmgr 6 7 import ( 8 "fmt" 9 "math" 10 "sync" 11 "time" 12 ) 13 14 const ( 15 // Halflife defines the time (in seconds) by which the transient part 16 // of the ban score decays to one half of it's original value. 17 Halflife = 60 18 19 // lambda is the decaying constant. 20 lambda = math.Ln2 / Halflife 21 22 // Lifetime defines the maximum age of the transient part of the ban 23 // score to be considered a non-zero score (in seconds). 24 Lifetime = 1800 25 26 // precomputedLen defines the amount of decay factors (one per second) that 27 // should be precomputed at initialization. 28 precomputedLen = 64 29 ) 30 31 // precomputedFactor stores precomputed exponential decay factors for the first 32 // 'precomputedLen' seconds starting from t == 0. 33 var precomputedFactor [precomputedLen]float64 34 35 // init precomputes decay factors. 36 func init() { 37 for i := range precomputedFactor { 38 precomputedFactor[i] = math.Exp(-1.0 * float64(i) * lambda) 39 } 40 } 41 42 // decayFactor returns the decay factor at t seconds, using precalculated values 43 // if available, or calculating the factor if needed. 44 func decayFactor(t int64) float64 { 45 if t < precomputedLen { 46 return precomputedFactor[t] 47 } 48 return math.Exp(-1.0 * float64(t) * lambda) 49 } 50 51 // DynamicBanScore provides dynamic ban scores consisting of a persistent and a 52 // decaying component. The persistent score could be utilized to create simple 53 // additive banning policies similar to those found in other bitcoin node 54 // implementations. 55 // 56 // The decaying score enables the creation of evasive logic which handles 57 // misbehaving peers (especially application layer DoS attacks) gracefully 58 // by disconnecting and banning peers attempting various kinds of flooding. 59 // DynamicBanScore allows these two approaches to be used in tandem. 60 // 61 // Zero value: Values of type DynamicBanScore are immediately ready for use upon 62 // declaration. 63 type DynamicBanScore struct { 64 lastUnix int64 65 transient float64 66 persistent uint32 67 mtx sync.Mutex 68 } 69 70 // String returns the ban score as a human-readable string. 71 func (s *DynamicBanScore) String() string { 72 s.mtx.Lock() 73 r := fmt.Sprintf("persistent %v + transient %v at %v = %v as of now", 74 s.persistent, s.transient, s.lastUnix, s.int(time.Now())) 75 s.mtx.Unlock() 76 return r 77 } 78 79 // Int returns the current ban score, the sum of the persistent and decaying 80 // scores. 81 // 82 // This function is safe for concurrent access. 83 func (s *DynamicBanScore) Int() uint32 { 84 s.mtx.Lock() 85 r := s.int(time.Now()) 86 s.mtx.Unlock() 87 return r 88 } 89 90 // Increase increases both the persistent and decaying scores by the values 91 // passed as parameters. The resulting score is returned. 92 // 93 // This function is safe for concurrent access. 94 func (s *DynamicBanScore) Increase(persistent, transient uint32) uint32 { 95 s.mtx.Lock() 96 r := s.increase(persistent, transient, time.Now()) 97 s.mtx.Unlock() 98 return r 99 } 100 101 // Reset set both persistent and decaying scores to zero. 102 // 103 // This function is safe for concurrent access. 104 func (s *DynamicBanScore) Reset() { 105 s.mtx.Lock() 106 s.persistent = 0 107 s.transient = 0 108 s.lastUnix = 0 109 s.mtx.Unlock() 110 } 111 112 // int returns the ban score, the sum of the persistent and decaying scores at a 113 // given point in time. 114 // 115 // This function is not safe for concurrent access. It is intended to be used 116 // internally and during testing. 117 func (s *DynamicBanScore) int(t time.Time) uint32 { 118 dt := t.Unix() - s.lastUnix 119 if s.transient < 1 || dt < 0 || Lifetime < dt { 120 return s.persistent 121 } 122 return s.persistent + uint32(s.transient*decayFactor(dt)) 123 } 124 125 // increase increases the persistent, the decaying or both scores by the values 126 // passed as parameters. The resulting score is calculated as if the action was 127 // carried out at the point time represented by the third parameter. The 128 // resulting score is returned. 129 // 130 // This function is not safe for concurrent access. 131 func (s *DynamicBanScore) increase(persistent, transient uint32, t time.Time) uint32 { 132 s.persistent += persistent 133 tu := t.Unix() 134 dt := tu - s.lastUnix 135 136 if transient > 0 { 137 if Lifetime < dt { 138 s.transient = 0 139 } else if s.transient > 1 && dt > 0 { 140 s.transient *= decayFactor(dt) 141 } 142 s.transient += float64(transient) 143 s.lastUnix = tu 144 } 145 return s.persistent + uint32(s.transient) 146 }