dynamicbanscore.go raw

   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