github.com/dashpay/godash@v0.0.0-20160726055534-e038a21e0e3d/dynamicbanscore.go (about)

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