package swarm
import (
"fmt"
"sync"
ma "github.com/multiformats/go-multiaddr"
manet "github.com/multiformats/go-multiaddr/net"
)
type BlackHoleState int
const (
blackHoleStateProbing BlackHoleState = iota
blackHoleStateAllowed
blackHoleStateBlocked
)
func (st BlackHoleState ) String () string {
switch st {
case blackHoleStateProbing :
return "Probing"
case blackHoleStateAllowed :
return "Allowed"
case blackHoleStateBlocked :
return "Blocked"
default :
return fmt .Sprintf ("Unknown %d" , st )
}
}
type BlackHoleSuccessCounter struct {
N int
MinSuccesses int
Name string
mu sync .Mutex
requests int
dialResults []bool
successes int
state BlackHoleState
}
func (b *BlackHoleSuccessCounter ) RecordResult (success bool ) {
b .mu .Lock ()
defer b .mu .Unlock ()
if b .state == blackHoleStateBlocked && success {
b .reset ()
return
}
if success {
b .successes ++
}
b .dialResults = append (b .dialResults , success )
if len (b .dialResults ) > b .N {
if b .dialResults [0 ] {
b .successes --
}
b .dialResults = b .dialResults [1 :]
}
b .updateState ()
}
func (b *BlackHoleSuccessCounter ) HandleRequest () BlackHoleState {
b .mu .Lock ()
defer b .mu .Unlock ()
b .requests ++
if b .state == blackHoleStateAllowed {
return blackHoleStateAllowed
} else if b .state == blackHoleStateProbing || b .requests %b .N == 0 {
return blackHoleStateProbing
} else {
return blackHoleStateBlocked
}
}
func (b *BlackHoleSuccessCounter ) reset () {
b .successes = 0
b .dialResults = b .dialResults [:0 ]
b .requests = 0
b .updateState ()
}
func (b *BlackHoleSuccessCounter ) updateState () {
st := b .state
if len (b .dialResults ) < b .N {
b .state = blackHoleStateProbing
} else if b .successes >= b .MinSuccesses {
b .state = blackHoleStateAllowed
} else {
b .state = blackHoleStateBlocked
}
if st != b .state {
log .Debugf ("%s blackHoleDetector state changed from %s to %s" , b .Name , st , b .state )
}
}
func (b *BlackHoleSuccessCounter ) State () BlackHoleState {
b .mu .Lock ()
defer b .mu .Unlock ()
return b .state
}
type blackHoleInfo struct {
name string
state BlackHoleState
nextProbeAfter int
successFraction float64
}
func (b *BlackHoleSuccessCounter ) info () blackHoleInfo {
b .mu .Lock ()
defer b .mu .Unlock ()
nextProbeAfter := 0
if b .state == blackHoleStateBlocked {
nextProbeAfter = b .N - (b .requests % b .N )
}
successFraction := 0.0
if len (b .dialResults ) > 0 {
successFraction = float64 (b .successes ) / float64 (len (b .dialResults ))
}
return blackHoleInfo {
name : b .Name ,
state : b .state ,
nextProbeAfter : nextProbeAfter ,
successFraction : successFraction ,
}
}
type blackHoleDetector struct {
udp, ipv6 *BlackHoleSuccessCounter
mt MetricsTracer
readOnly bool
}
func (d *blackHoleDetector ) FilterAddrs (addrs []ma .Multiaddr ) (valid []ma .Multiaddr , blackHoled []ma .Multiaddr ) {
hasUDP , hasIPv6 := false , false
for _ , a := range addrs {
if !manet .IsPublicAddr (a ) {
continue
}
if isProtocolAddr (a , ma .P_UDP ) {
hasUDP = true
}
if isProtocolAddr (a , ma .P_IP6 ) {
hasIPv6 = true
}
}
udpRes := blackHoleStateAllowed
if d .udp != nil && hasUDP {
udpRes = d .getFilterState (d .udp )
d .trackMetrics (d .udp )
}
ipv6Res := blackHoleStateAllowed
if d .ipv6 != nil && hasIPv6 {
ipv6Res = d .getFilterState (d .ipv6 )
d .trackMetrics (d .ipv6 )
}
blackHoled = make ([]ma .Multiaddr , 0 , len (addrs ))
return ma .FilterAddrs (
addrs ,
func (a ma .Multiaddr ) bool {
if !manet .IsPublicAddr (a ) {
return true
}
if udpRes == blackHoleStateProbing && isProtocolAddr (a , ma .P_UDP ) {
return true
}
if ipv6Res == blackHoleStateProbing && isProtocolAddr (a , ma .P_IP6 ) {
return true
}
if udpRes == blackHoleStateBlocked && isProtocolAddr (a , ma .P_UDP ) {
blackHoled = append (blackHoled , a )
return false
}
if ipv6Res == blackHoleStateBlocked && isProtocolAddr (a , ma .P_IP6 ) {
blackHoled = append (blackHoled , a )
return false
}
return true
},
), blackHoled
}
func (d *blackHoleDetector ) RecordResult (addr ma .Multiaddr , success bool ) {
if d .readOnly || !manet .IsPublicAddr (addr ) {
return
}
if d .udp != nil && isProtocolAddr (addr , ma .P_UDP ) {
d .udp .RecordResult (success )
d .trackMetrics (d .udp )
}
if d .ipv6 != nil && isProtocolAddr (addr , ma .P_IP6 ) {
d .ipv6 .RecordResult (success )
d .trackMetrics (d .ipv6 )
}
}
func (d *blackHoleDetector ) getFilterState (f *BlackHoleSuccessCounter ) BlackHoleState {
if d .readOnly {
if f .State () != blackHoleStateAllowed {
return blackHoleStateBlocked
}
return blackHoleStateAllowed
}
return f .HandleRequest ()
}
func (d *blackHoleDetector ) trackMetrics (f *BlackHoleSuccessCounter ) {
if d .readOnly || d .mt == nil {
return
}
info := f .info ()
d .mt .UpdatedBlackHoleSuccessCounter (info .name , info .state , info .nextProbeAfter , info .successFraction )
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .