forked from gcash/bchd
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement banning based on dynamic ban scores
Dynamic ban scores consist of a persistent and a decaying component. The persistent score can be used to create simple additive banning policies simlar to those found in other bitcoin node implementations. The decaying score enables the creation of evasive logic which handles misbehaving peers (especially application layer DoS attacks) gracefully by disconnecting and banning peers attempting various kinds of flooding. Dynamic ban scores allow these two approaches to be used in tandem. This pull request includes the following: - Dynamic ban score type & functions, with tests for core functionality - Ban score of connected peers can be queried via rpc (getpeerinfo) - Example policy with decaying score increments on mempool and getdata - Logging of misbehavior once half of the ban threshold is reached - Banning logic can be disabled via configuration (enabled by default) - User defined ban threshold can be set via configuration
- Loading branch information
Showing
7 changed files
with
281 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
// Copyright (c) 2016 The btcsuite developers | ||
// Use of this source code is governed by an ISC | ||
// license that can be found in the LICENSE file. | ||
|
||
package main | ||
|
||
import ( | ||
"fmt" | ||
"math" | ||
"sync" | ||
"time" | ||
) | ||
|
||
const ( | ||
// Halflife defines the time (in seconds) by which the transient part | ||
// of the ban score decays to one half of it's original value. | ||
Halflife = 60 | ||
|
||
// lambda is the decaying constant. | ||
lambda = math.Ln2 / Halflife | ||
|
||
// Lifetime defines the maximum age of the transient part of the ban | ||
// score to be considered a non-zero score (in seconds). | ||
Lifetime = 1800 | ||
|
||
// precomputedLen defines the amount of decay factors (one per second) that | ||
// should be precomputed at initialization. | ||
precomputedLen = 64 | ||
) | ||
|
||
// precomputedFactor stores precomputed exponential decay factors for the first | ||
// 'precomputedLen' seconds starting from t == 0. | ||
var precomputedFactor [precomputedLen]float64 | ||
|
||
// init precomputes decay factors. | ||
func init() { | ||
for i := range precomputedFactor { | ||
precomputedFactor[i] = math.Exp(-1.0 * float64(i) * lambda) | ||
} | ||
} | ||
|
||
// decayFactor returns the decay factor at t seconds, using precalculated values | ||
// if available, or calculating the factor if needed. | ||
func decayFactor(t int64) float64 { | ||
if t < precomputedLen { | ||
return precomputedFactor[t] | ||
} | ||
return math.Exp(-1.0 * float64(t) * lambda) | ||
} | ||
|
||
// dynamicBanScore provides dynamic ban scores consisting of a persistent and a | ||
// decaying component. The persistent score could be utilized to create simple | ||
// additive banning policies similar to those found in other bitcoin node | ||
// implementations. | ||
// | ||
// The decaying score enables the creation of evasive logic which handles | ||
// misbehaving peers (especially application layer DoS attacks) gracefully | ||
// by disconnecting and banning peers attempting various kinds of flooding. | ||
// dynamicBanScore allows these two approaches to be used in tandem. | ||
// | ||
// Zero value: Values of type dynamicBanScore are immediately ready for use upon | ||
// declaration. | ||
type dynamicBanScore struct { | ||
lastUnix int64 | ||
transient float64 | ||
persistent uint32 | ||
sync.Mutex | ||
} | ||
|
||
// String returns the ban score as a human-readable string. | ||
func (s *dynamicBanScore) String() string { | ||
s.Lock() | ||
r := fmt.Sprintf("persistent %v + transient %v at %v = %v as of now", | ||
s.persistent, s.transient, s.lastUnix, s.Int()) | ||
s.Unlock() | ||
return r | ||
} | ||
|
||
// Int returns the current ban score, the sum of the persistent and decaying | ||
// scores. | ||
// | ||
// This function is safe for concurrent access. | ||
func (s *dynamicBanScore) Int() uint32 { | ||
s.Lock() | ||
r := s.int(time.Now()) | ||
s.Unlock() | ||
return r | ||
} | ||
|
||
// Increase increases both the persistent and decaying scores by the values | ||
// passed as parameters. The resulting score is returned. | ||
// | ||
// This function is safe for concurrent access. | ||
func (s *dynamicBanScore) Increase(persistent, transient uint32) uint32 { | ||
s.Lock() | ||
r := s.increase(persistent, transient, time.Now()) | ||
s.Unlock() | ||
return r | ||
} | ||
|
||
// Reset set both persistent and decaying scores to zero. | ||
// | ||
// This function is safe for concurrent access. | ||
func (s *dynamicBanScore) Reset() { | ||
s.Lock() | ||
s.persistent = 0 | ||
s.transient = 0 | ||
s.lastUnix = 0 | ||
s.Unlock() | ||
} | ||
|
||
// int returns the ban score, the sum of the persistent and decaying scores at a | ||
// given point in time. | ||
// | ||
// This function is not safe for concurrent access. It is intended to be used | ||
// internally and during testing. | ||
func (s *dynamicBanScore) int(t time.Time) uint32 { | ||
dt := t.Unix() - s.lastUnix | ||
if s.transient < 1 || dt < 0 || Lifetime < dt { | ||
return s.persistent | ||
} | ||
return s.persistent + uint32(s.transient*decayFactor(dt)) | ||
} | ||
|
||
// increase increases the persistent, the decaying or both scores by the values | ||
// passed as parameters. The resulting score is calculated as if the action was | ||
// carried out at the point time represented by the third paramter. The | ||
// resulting score is returned. | ||
// | ||
// This function is not safe for concurrent access. | ||
func (s *dynamicBanScore) increase(persistent, transient uint32, t time.Time) uint32 { | ||
s.persistent += persistent | ||
tu := t.Unix() | ||
dt := tu - s.lastUnix | ||
|
||
if transient > 0 { | ||
if Lifetime < dt { | ||
s.transient = 0 | ||
} else if s.transient > 1 && dt > 0 { | ||
s.transient *= decayFactor(dt) | ||
} | ||
s.transient += float64(transient) | ||
s.lastUnix = tu | ||
} | ||
return s.persistent + uint32(s.transient) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
// Copyright (c) 2016 The btcsuite developers | ||
// Use of this source code is governed by an ISC | ||
// license that can be found in the LICENSE file. | ||
|
||
package main | ||
|
||
import ( | ||
"math" | ||
"testing" | ||
"time" | ||
) | ||
|
||
// TestDynamicBanScoreDecay tests the exponential decay implemented in | ||
// dynamicBanScore. | ||
func TestDynamicBanScoreDecay(t *testing.T) { | ||
var bs dynamicBanScore | ||
base := time.Now() | ||
|
||
r := bs.increase(100, 50, base) | ||
if r != 150 { | ||
t.Errorf("Unexpected result %d after ban score increase.", r) | ||
} | ||
|
||
r = bs.int(base.Add(time.Minute)) | ||
if r != 125 { | ||
t.Errorf("Halflife check failed - %d instead of 125", r) | ||
} | ||
|
||
r = bs.int(base.Add(7 * time.Minute)) | ||
if r != 100 { | ||
t.Errorf("Decay after 7m - %d instead of 100", r) | ||
} | ||
} | ||
|
||
// TestDynamicBanScoreLifetime tests that dynamicBanScore properly yields zero | ||
// once the maximum age is reached. | ||
func TestDynamicBanScoreLifetime(t *testing.T) { | ||
var bs dynamicBanScore | ||
base := time.Now() | ||
|
||
r := bs.increase(0, math.MaxUint32, base) | ||
r = bs.int(base.Add(Lifetime * time.Second)) | ||
if r != 3 { // 3, not 4 due to precision loss and truncating 3.999... | ||
t.Errorf("Pre max age check with MaxUint32 failed - %d", r) | ||
} | ||
r = bs.int(base.Add((Lifetime + 1) * time.Second)) | ||
if r != 0 { | ||
t.Errorf("Zero after max age check failed - %d instead of 0", r) | ||
} | ||
} | ||
|
||
// TestDynamicBanScore tests exported functions of dynamicBanScore. Exponential | ||
// decay or other time based behavior is tested by other functions. | ||
func TestDynamicBanScoreReset(t *testing.T) { | ||
var bs dynamicBanScore | ||
if bs.Int() != 0 { | ||
t.Errorf("Initial state is not zero.") | ||
} | ||
bs.Increase(100, 0) | ||
r := bs.Int() | ||
if r != 100 { | ||
t.Errorf("Unexpected result %d after ban score increase.", r) | ||
} | ||
bs.Reset() | ||
if bs.Int() != 0 { | ||
t.Errorf("Failed to reset ban score.") | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters