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  }