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