You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

701 lines
16 KiB

// Modified for Tendermint
// Originally Copyright (c) 2013-2014 Conformal Systems LLC.
// https://github.com/conformal/btcd/blob/master/LICENSE
package p2p
import (
crand "crypto/rand" // for seeding
"encoding/binary"
"encoding/json"
"fmt"
"io"
"math"
"math/rand"
"net"
"os"
"sync"
"sync/atomic"
"time"
. "github.com/tendermint/tendermint/binary"
)
/* AddrBook - concurrency safe peer address manager */
type AddrBook struct {
filePath string
mtx sync.Mutex
rand *rand.Rand
key [32]byte
addrIndex map[string]*knownAddress // new & old
addrNew [newBucketCount]map[string]*knownAddress
addrOld [oldBucketCount][]*knownAddress
started int32
shutdown int32
wg sync.WaitGroup
quit chan struct{}
nOld int
nNew int
}
const (
// addresses under which the address manager will claim to need more addresses.
needAddressThreshold = 1000
// interval used to dump the address cache to disk for future use.
dumpAddressInterval = time.Minute * 2
// max addresses in each old address bucket.
oldBucketSize = 64
// buckets we split old addresses over.
oldBucketCount = 64
// max addresses in each new address bucket.
newBucketSize = 64
// buckets that we spread new addresses over.
newBucketCount = 256
// old buckets over which an address group will be spread.
oldBucketsPerGroup = 4
// new buckets over which an source address group will be spread.
newBucketsPerGroup = 32
// buckets a frequently seen new address may end up in.
newBucketsPerAddress = 4
// days before which we assume an address has vanished
// if we have not seen it announced in that long.
numMissingDays = 30
// tries without a single success before we assume an address is bad.
numRetries = 3
// max failures we will accept without a success before considering an address bad.
maxFailures = 10
// days since the last success before we will consider evicting an address.
minBadDays = 7
// max addresses that we will send in response to a GetSelection
getSelectionMax = 2500
// % of total addresses known that we will share with a call to GetSelection
getSelectionPercent = 23
// current version of the on-disk format.
serializationVersion = 1
)
// Use Start to begin processing asynchronous address updates.
func NewAddrBook(filePath string) *AddrBook {
am := AddrBook{
rand: rand.New(rand.NewSource(time.Now().UnixNano())),
quit: make(chan struct{}),
filePath: filePath,
}
am.init()
return &am
}
// When modifying this, don't forget to update loadFromFile()
func (a *AddrBook) init() {
a.addrIndex = make(map[string]*knownAddress)
io.ReadFull(crand.Reader, a.key[:])
for i := range a.addrNew {
a.addrNew[i] = make(map[string]*knownAddress)
}
for i := range a.addrOld {
a.addrOld[i] = make([]*knownAddress, 0, oldBucketSize)
}
}
func (a *AddrBook) Start() {
if atomic.AddInt32(&a.started, 1) != 1 {
return
}
log.Trace("Starting address manager")
a.loadFromFile(a.filePath)
a.wg.Add(1)
go a.addressHandler()
}
func (a *AddrBook) Stop() {
if atomic.AddInt32(&a.shutdown, 1) != 1 {
return
}
log.Infof("Address manager shutting down")
close(a.quit)
a.wg.Wait()
}
func (a *AddrBook) AddAddress(addr *NetAddress, src *NetAddress) {
a.mtx.Lock()
defer a.mtx.Unlock()
a.addAddress(addr, src)
}
func (a *AddrBook) NeedMoreAddresses() bool {
return a.Size() < needAddressThreshold
}
func (a *AddrBook) Size() int {
a.mtx.Lock()
defer a.mtx.Unlock()
return a.size()
}
func (a *AddrBook) size() int {
return a.nNew + a.nOld
}
// Pick an address to connect to with new/old bias.
func (a *AddrBook) PickAddress(newBias int) *knownAddress {
a.mtx.Lock()
defer a.mtx.Unlock()
if a.nOld == 0 && a.nNew == 0 {
return nil
}
if newBias > 100 {
newBias = 100
}
if newBias < 0 {
newBias = 0
}
// Bias between new and old addresses.
oldCorrelation := math.Sqrt(float64(a.nOld)) * (100.0 - float64(newBias))
newCorrelation := math.Sqrt(float64(a.nNew)) * float64(newBias)
if (newCorrelation+oldCorrelation)*a.rand.Float64() < oldCorrelation {
// pick random Old bucket.
var bucket []*knownAddress = nil
for len(bucket) == 0 {
bucket = a.addrOld[a.rand.Intn(len(a.addrOld))]
}
// pick a random ka from bucket.
return bucket[a.rand.Intn(len(bucket))]
} else {
// pick random New bucket.
var bucket map[string]*knownAddress = nil
for len(bucket) == 0 {
bucket = a.addrNew[a.rand.Intn(len(a.addrNew))]
}
// pick a random ka from bucket.
randIndex := a.rand.Intn(len(bucket))
for _, ka := range bucket {
randIndex--
if randIndex == 0 {
return ka
}
}
panic("Should not happen")
}
return nil
}
func (a *AddrBook) MarkGood(addr *NetAddress) {
a.mtx.Lock()
defer a.mtx.Unlock()
ka := a.addrIndex[addr.String()]
if ka == nil {
return
}
ka.MarkGood()
if ka.OldBucket == -1 {
a.moveToOld(ka)
}
}
func (a *AddrBook) MarkAttempt(addr *NetAddress) {
a.mtx.Lock()
defer a.mtx.Unlock()
ka := a.addrIndex[addr.String()]
if ka == nil {
return
}
ka.MarkAttempt()
}
/* Peer exchange */
// GetSelection randomly selects some addresses (old & new). Suitable for peer-exchange protocols.
func (a *AddrBook) GetSelection() []*NetAddress {
a.mtx.Lock()
defer a.mtx.Unlock()
if a.size() == 0 {
return nil
}
allAddr := make([]*NetAddress, a.size())
i := 0
for _, v := range a.addrIndex {
allAddr[i] = v.Addr
i++
}
numAddresses := len(allAddr) * getSelectionPercent / 100
if numAddresses > getSelectionMax {
numAddresses = getSelectionMax
}
// Fisher-Yates shuffle the array. We only need to do the first
// `numAddresses' since we are throwing the rest.
for i := 0; i < numAddresses; i++ {
// pick a number between current index and the end
j := rand.Intn(len(allAddr)-i) + i
allAddr[i], allAddr[j] = allAddr[j], allAddr[i]
}
// slice off the limit we are willing to share.
return allAddr[:numAddresses]
}
/* Loading & Saving */
type addrBookJSON struct {
Key [32]byte
AddrNew [newBucketCount][]*knownAddress
AddrOld [oldBucketCount][]*knownAddress
NumOld int
NumNew int
}
func (a *AddrBook) saveToFile(filePath string) {
// turn a.addrNew into an array like a.addrOld
__addrNew := [newBucketCount][]*knownAddress{}
for i, newBucket := range a.addrNew {
var array []*knownAddress = make([]*knownAddress, 0)
for _, ka := range newBucket {
array = append(array, ka)
}
__addrNew[i] = array
}
aJSON := &addrBookJSON{
Key: a.key,
AddrNew: __addrNew,
AddrOld: a.addrOld,
NumOld: a.nOld,
NumNew: a.nNew,
}
w, err := os.Create(filePath)
if err != nil {
log.Error("Error opening file: ", filePath, err)
return
}
enc := json.NewEncoder(w)
defer w.Close()
err = enc.Encode(&aJSON)
if err != nil {
panic(err)
}
}
func (a *AddrBook) loadFromFile(filePath string) {
// If doesn't exist, do nothing.
_, err := os.Stat(filePath)
if os.IsNotExist(err) {
return
}
// Load addrBookJSON{}
r, err := os.Open(filePath)
if err != nil {
panic(fmt.Errorf("%s error opening file: %v", filePath, err))
}
defer r.Close()
aJSON := &addrBookJSON{}
dec := json.NewDecoder(r)
err = dec.Decode(aJSON)
if err != nil {
panic(fmt.Errorf("error reading %s: %v", filePath, err))
}
// Now we need to restore the fields
// Restore the key
copy(a.key[:], aJSON.Key[:])
// Restore .addrNew
for i, newBucket := range aJSON.AddrNew {
for _, ka := range newBucket {
a.addrNew[i][ka.Addr.String()] = ka
a.addrIndex[ka.Addr.String()] = ka
}
}
// Restore .addrOld
for i, oldBucket := range aJSON.AddrOld {
copy(a.addrOld[i], oldBucket)
for _, ka := range oldBucket {
a.addrIndex[ka.Addr.String()] = ka
}
}
// Restore simple fields
a.nNew = aJSON.NumNew
a.nOld = aJSON.NumOld
}
/* Private methods */
func (a *AddrBook) addressHandler() {
dumpAddressTicker := time.NewTicker(dumpAddressInterval)
out:
for {
select {
case <-dumpAddressTicker.C:
a.saveToFile(a.filePath)
case <-a.quit:
break out
}
}
dumpAddressTicker.Stop()
a.saveToFile(a.filePath)
a.wg.Done()
log.Trace("Address handler done")
}
func (a *AddrBook) addAddress(addr, src *NetAddress) {
if !addr.Routable() {
return
}
key := addr.String()
ka := a.addrIndex[key]
if ka != nil {
// Already added
if ka.OldBucket != -1 {
return
}
if ka.NewRefs == newBucketsPerAddress {
return
}
// The more entries we have, the less likely we are to add more.
factor := int32(2 * ka.NewRefs)
if a.rand.Int31n(factor) != 0 {
return
}
} else {
ka = NewknownAddress(addr, src)
a.addrIndex[key] = ka
a.nNew++
}
bucket := a.getNewBucket(addr, src)
// Already exists?
if _, ok := a.addrNew[bucket][key]; ok {
return
}
// Enforce max addresses.
if len(a.addrNew[bucket]) > newBucketSize {
log.Tracef("new bucket is full, expiring old ")
a.expireNew(bucket)
}
// Add to new bucket.
ka.NewRefs++
a.addrNew[bucket][key] = ka
log.Tracef("Added new address %s for a total of %d addresses", addr, a.nOld+a.nNew)
}
// Make space in the new buckets by expiring the really bad entries.
// If no bad entries are available we look at a few and remove the oldest.
func (a *AddrBook) expireNew(bucket int) {
var oldest *knownAddress
for k, v := range a.addrNew[bucket] {
// If an entry is bad, throw it away
if v.IsBad() {
log.Tracef("expiring bad address %v", k)
delete(a.addrNew[bucket], k)
v.NewRefs--
if v.NewRefs == 0 {
a.nNew--
delete(a.addrIndex, k)
}
return
}
// or, keep track of the oldest entry
if oldest == nil {
oldest = v
} else if v.LastAttempt.Before(oldest.LastAttempt.Time) {
oldest = v
}
}
// If we haven't thrown out a bad entry, throw out the oldest entry
if oldest != nil {
key := oldest.Addr.String()
log.Tracef("expiring oldest address %v", key)
delete(a.addrNew[bucket], key)
oldest.NewRefs--
if oldest.NewRefs == 0 {
a.nNew--
delete(a.addrIndex, key)
}
}
}
func (a *AddrBook) moveToOld(ka *knownAddress) {
// Remove from all new buckets.
// Remember one of those new buckets.
addrKey := ka.Addr.String()
freedBucket := -1
for i := range a.addrNew {
// we check for existance so we can record the first one
if _, ok := a.addrNew[i][addrKey]; ok {
delete(a.addrNew[i], addrKey)
ka.NewRefs--
if freedBucket == -1 {
freedBucket = i
}
}
}
a.nNew--
if freedBucket == -1 {
panic("Expected to find addr in at least one new bucket")
}
oldBucket := a.getOldBucket(ka.Addr)
// If room in oldBucket, put it in.
if len(a.addrOld[oldBucket]) < oldBucketSize {
ka.OldBucket = Int16(oldBucket)
a.addrOld[oldBucket] = append(a.addrOld[oldBucket], ka)
a.nOld++
return
}
// No room, we have to evict something else.
rmkaIndex := a.pickOld(oldBucket)
rmka := a.addrOld[oldBucket][rmkaIndex]
// Find a new bucket to put rmka in.
newBucket := a.getNewBucket(rmka.Addr, rmka.Src)
if len(a.addrNew[newBucket]) >= newBucketSize {
newBucket = freedBucket
}
// Replace with ka in list.
ka.OldBucket = Int16(oldBucket)
a.addrOld[oldBucket][rmkaIndex] = ka
rmka.OldBucket = -1
// Put rmka into new bucket
rmkey := rmka.Addr.String()
log.Tracef("Replacing %s with %s in old", rmkey, addrKey)
a.addrNew[newBucket][rmkey] = rmka
rmka.NewRefs++
a.nNew++
}
// Returns the index in old bucket of oldest entry.
func (a *AddrBook) pickOld(bucket int) int {
var oldest *knownAddress
var oldestIndex int
for i, ka := range a.addrOld[bucket] {
if oldest == nil || ka.LastAttempt.Before(oldest.LastAttempt.Time) {
oldest = ka
oldestIndex = i
}
}
return oldestIndex
}
// doublesha256(key + sourcegroup +
// int64(doublesha256(key + group + sourcegroup))%bucket_per_source_group) % num_new_buckes
func (a *AddrBook) getNewBucket(addr, src *NetAddress) int {
data1 := []byte{}
data1 = append(data1, a.key[:]...)
data1 = append(data1, []byte(GroupKey(addr))...)
data1 = append(data1, []byte(GroupKey(src))...)
hash1 := DoubleSha256(data1)
hash64 := binary.LittleEndian.Uint64(hash1)
hash64 %= newBucketsPerGroup
var hashbuf [8]byte
binary.LittleEndian.PutUint64(hashbuf[:], hash64)
data2 := []byte{}
data2 = append(data2, a.key[:]...)
data2 = append(data2, GroupKey(src)...)
data2 = append(data2, hashbuf[:]...)
hash2 := DoubleSha256(data2)
return int(binary.LittleEndian.Uint64(hash2) % newBucketCount)
}
// doublesha256(key + group + truncate_to_64bits(doublesha256(key + addr))%buckets_per_group) % num_buckets
func (a *AddrBook) getOldBucket(addr *NetAddress) int {
data1 := []byte{}
data1 = append(data1, a.key[:]...)
data1 = append(data1, []byte(addr.String())...)
hash1 := DoubleSha256(data1)
hash64 := binary.LittleEndian.Uint64(hash1)
hash64 %= oldBucketsPerGroup
var hashbuf [8]byte
binary.LittleEndian.PutUint64(hashbuf[:], hash64)
data2 := []byte{}
data2 = append(data2, a.key[:]...)
data2 = append(data2, GroupKey(addr)...)
data2 = append(data2, hashbuf[:]...)
hash2 := DoubleSha256(data2)
return int(binary.LittleEndian.Uint64(hash2) % oldBucketCount)
}
// Return a string representing the network group of this address.
// This is the /16 for IPv6, the /32 (/36 for he.net) for IPv6, the string
// "local" for a local address and the string "unroutable for an unroutable
// address.
func GroupKey(na *NetAddress) string {
if na.Local() {
return "local"
}
if !na.Routable() {
return "unroutable"
}
if ipv4 := na.IP.To4(); ipv4 != nil {
return (&net.IPNet{IP: na.IP, Mask: net.CIDRMask(16, 32)}).String()
}
if na.RFC6145() || na.RFC6052() {
// last four bytes are the ip address
ip := net.IP(na.IP[12:16])
return (&net.IPNet{IP: ip, Mask: net.CIDRMask(16, 32)}).String()
}
if na.RFC3964() {
ip := net.IP(na.IP[2:7])
return (&net.IPNet{IP: ip, Mask: net.CIDRMask(16, 32)}).String()
}
if na.RFC4380() {
// teredo tunnels have the last 4 bytes as the v4 address XOR
// 0xff.
ip := net.IP(make([]byte, 4))
for i, byte := range na.IP[12:16] {
ip[i] = byte ^ 0xff
}
return (&net.IPNet{IP: ip, Mask: net.CIDRMask(16, 32)}).String()
}
// OK, so now we know ourselves to be a IPv6 address.
// bitcoind uses /32 for everything, except for Hurricane Electric's
// (he.net) IP range, which it uses /36 for.
bits := 32
heNet := &net.IPNet{IP: net.ParseIP("2001:470::"),
Mask: net.CIDRMask(32, 128)}
if heNet.Contains(na.IP) {
bits = 36
}
return (&net.IPNet{IP: na.IP, Mask: net.CIDRMask(bits, 128)}).String()
}
/*
knownAddress
tracks information about a known network address that is used
to determine how viable an address is.
*/
type knownAddress struct {
Addr *NetAddress
Src *NetAddress
Attempts UInt32
LastAttempt Time
LastSuccess Time
NewRefs UInt16
OldBucket Int16
}
func NewknownAddress(addr *NetAddress, src *NetAddress) *knownAddress {
return &knownAddress{
Addr: addr,
Src: src,
OldBucket: -1,
LastAttempt: Time{time.Now()},
Attempts: 0,
}
}
func ReadknownAddress(r io.Reader) *knownAddress {
return &knownAddress{
Addr: ReadNetAddress(r),
Src: ReadNetAddress(r),
Attempts: ReadUInt32(r),
LastAttempt: ReadTime(r),
LastSuccess: ReadTime(r),
NewRefs: ReadUInt16(r),
OldBucket: ReadInt16(r),
}
}
func (ka *knownAddress) WriteTo(w io.Writer) (n int64, err error) {
n, err = WriteOnto(ka.Addr, w, n, err)
n, err = WriteOnto(ka.Src, w, n, err)
n, err = WriteOnto(ka.Attempts, w, n, err)
n, err = WriteOnto(ka.LastAttempt, w, n, err)
n, err = WriteOnto(ka.LastSuccess, w, n, err)
n, err = WriteOnto(ka.NewRefs, w, n, err)
n, err = WriteOnto(ka.OldBucket, w, n, err)
return
}
func (ka *knownAddress) MarkAttempt() {
now := Time{time.Now()}
ka.LastAttempt = now
ka.Attempts += 1
}
func (ka *knownAddress) MarkGood() {
now := Time{time.Now()}
ka.LastAttempt = now
ka.Attempts = 0
ka.LastSuccess = now
}
/*
An address is bad if the address in question has not been tried in the last
minute and meets one of the following criteria:
1) It claims to be from the future
2) It hasn't been seen in over a month
3) It has failed at least three times and never succeeded
4) It has failed ten times in the last week
All addresses that meet these criteria are assumed to be worthless and not
worth keeping hold of.
*/
func (ka *knownAddress) IsBad() bool {
// Has been attempted in the last minute --> good
if ka.LastAttempt.Before(time.Now().Add(-1 * time.Minute)) {
return false
}
// Over a month old?
if ka.LastAttempt.After(time.Now().Add(-1 * numMissingDays * time.Hour * 24)) {
return true
}
// Never succeeded?
if ka.LastSuccess.IsZero() && ka.Attempts >= numRetries {
return true
}
// Hasn't succeeded in too long?
if ka.LastSuccess.Before(time.Now().Add(-1*minBadDays*time.Hour*24)) &&
ka.Attempts >= maxFailures {
return true
}
return false
}