mirror of
https://github.com/tendermint/tendermint.git
synced 2026-09-19 06:31:57 +00:00
Refactor to move common libraries out of project
This commit is contained in:
@@ -1,77 +0,0 @@
|
||||
# `tendermint/p2p`
|
||||
|
||||
`tendermint/p2p` provides an abstraction around peer-to-peer communication.<br/>
|
||||
|
||||
## Peer/MConnection/Channel
|
||||
|
||||
Each peer has one `MConnection` (multiplex connection) instance.
|
||||
|
||||
__multiplex__ *noun* a system or signal involving simultaneous transmission of
|
||||
several messages along a single channel of communication.
|
||||
|
||||
Each `MConnection` handles message transmission on multiple abstract communication
|
||||
`Channel`s. Each channel has a globally unique byte id.
|
||||
The byte id and the relative priorities of each `Channel` are configured upon
|
||||
initialization of the connection.
|
||||
|
||||
There are two methods for sending messages:
|
||||
```go
|
||||
func (m MConnection) Send(chID byte, msg interface{}) bool {}
|
||||
func (m MConnection) TrySend(chID byte, msg interface{}) bool {}
|
||||
```
|
||||
|
||||
`Send(chID, msg)` is a blocking call that waits until `msg` is successfully queued
|
||||
for the channel with the given id byte `chID`. The message `msg` is serialized
|
||||
using the `tendermint/wire` submodule's `WriteBinary()` reflection routine.
|
||||
|
||||
`TrySend(chID, msg)` is a nonblocking call that returns false if the channel's
|
||||
queue is full.
|
||||
|
||||
`Send()` and `TrySend()` are also exposed for each `Peer`.
|
||||
|
||||
## Switch/Reactor
|
||||
|
||||
The `Switch` handles peer connections and exposes an API to receive incoming messages
|
||||
on `Reactors`. Each `Reactor` is responsible for handling incoming messages of one
|
||||
or more `Channels`. So while sending outgoing messages is typically performed on the peer,
|
||||
incoming messages are received on the reactor.
|
||||
|
||||
```go
|
||||
// Declare a MyReactor reactor that handles messages on MyChannelID.
|
||||
type MyReactor struct{}
|
||||
|
||||
func (reactor MyReactor) GetChannels() []*ChannelDescriptor {
|
||||
return []*ChannelDescriptor{ChannelDescriptor{ID:MyChannelID, Priority: 1}}
|
||||
}
|
||||
|
||||
func (reactor MyReactor) Receive(chID byte, peer *Peer, msgBytes []byte) {
|
||||
r, n, err := bytes.NewBuffer(msgBytes), new(int64), new(error)
|
||||
msgString := ReadString(r, n, err)
|
||||
fmt.Println(msgString)
|
||||
}
|
||||
|
||||
// Other Reactor methods omitted for brevity
|
||||
...
|
||||
|
||||
switch := NewSwitch([]Reactor{MyReactor{}})
|
||||
|
||||
...
|
||||
|
||||
// Send a random message to all outbound connections
|
||||
for _, peer := range switch.Peers().List() {
|
||||
if peer.IsOutbound() {
|
||||
peer.Send(MyChannelID, "Here's a random message")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### PexReactor/AddrBook
|
||||
|
||||
A `PEXReactor` reactor implementation is provided to automate peer discovery.
|
||||
|
||||
```go
|
||||
book := p2p.NewAddrBook(config.App.GetString("AddrBookFile"))
|
||||
pexReactor := p2p.NewPEXReactor(book)
|
||||
...
|
||||
switch := NewSwitch([]Reactor{pexReactor, myReactor, ...})
|
||||
```
|
||||
-813
@@ -1,813 +0,0 @@
|
||||
// Modified for Tendermint
|
||||
// Originally Copyright (c) 2013-2014 Conformal Systems LLC.
|
||||
// https://github.com/conformal/btcd/blob/master/LICENSE
|
||||
|
||||
package p2p
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"math"
|
||||
"math/rand"
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
. "github.com/tendermint/tendermint/common"
|
||||
)
|
||||
|
||||
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.
|
||||
maxNewBucketsPerAddress = 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
|
||||
|
||||
// % of total addresses known returned by GetSelection.
|
||||
getSelectionPercent = 23
|
||||
|
||||
// min addresses that must be returned by GetSelection. Useful for bootstrapping.
|
||||
minGetSelection = 32
|
||||
|
||||
// max addresses returned by GetSelection
|
||||
maxGetSelection = 2500
|
||||
|
||||
// current version of the on-disk format.
|
||||
serializationVersion = 1
|
||||
)
|
||||
|
||||
/* AddrBook - concurrency safe peer address manager */
|
||||
type AddrBook struct {
|
||||
QuitService
|
||||
|
||||
mtx sync.Mutex
|
||||
filePath string
|
||||
rand *rand.Rand
|
||||
key string
|
||||
ourAddrs map[string]*NetAddress
|
||||
addrLookup map[string]*knownAddress // new & old
|
||||
addrNew []map[string]*knownAddress
|
||||
addrOld []map[string]*knownAddress
|
||||
wg sync.WaitGroup
|
||||
nOld int
|
||||
nNew int
|
||||
}
|
||||
|
||||
const (
|
||||
bucketTypeNew = 0x01
|
||||
bucketTypeOld = 0x02
|
||||
)
|
||||
|
||||
// Use Start to begin processing asynchronous address updates.
|
||||
func NewAddrBook(filePath string) *AddrBook {
|
||||
am := &AddrBook{
|
||||
rand: rand.New(rand.NewSource(time.Now().UnixNano())),
|
||||
ourAddrs: make(map[string]*NetAddress),
|
||||
addrLookup: make(map[string]*knownAddress),
|
||||
filePath: filePath,
|
||||
}
|
||||
am.init()
|
||||
am.QuitService = *NewQuitService(log, "AddrBook", am)
|
||||
return am
|
||||
}
|
||||
|
||||
// When modifying this, don't forget to update loadFromFile()
|
||||
func (a *AddrBook) init() {
|
||||
a.key = CRandHex(24) // 24/2 * 8 = 96 bits
|
||||
// New addr buckets
|
||||
a.addrNew = make([]map[string]*knownAddress, newBucketCount)
|
||||
for i := range a.addrNew {
|
||||
a.addrNew[i] = make(map[string]*knownAddress)
|
||||
}
|
||||
// Old addr buckets
|
||||
a.addrOld = make([]map[string]*knownAddress, oldBucketCount)
|
||||
for i := range a.addrOld {
|
||||
a.addrOld[i] = make(map[string]*knownAddress)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *AddrBook) OnStart() error {
|
||||
a.QuitService.OnStart()
|
||||
a.loadFromFile(a.filePath)
|
||||
a.wg.Add(1)
|
||||
go a.saveRoutine()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *AddrBook) OnStop() {
|
||||
a.QuitService.OnStop()
|
||||
a.wg.Wait()
|
||||
}
|
||||
|
||||
func (a *AddrBook) AddOurAddress(addr *NetAddress) {
|
||||
a.mtx.Lock()
|
||||
defer a.mtx.Unlock()
|
||||
log.Info("Add our address to book", "addr", addr)
|
||||
a.ourAddrs[addr.String()] = addr
|
||||
}
|
||||
|
||||
func (a *AddrBook) OurAddresses() []*NetAddress {
|
||||
addrs := []*NetAddress{}
|
||||
for _, addr := range a.ourAddrs {
|
||||
addrs = append(addrs, addr)
|
||||
}
|
||||
return addrs
|
||||
}
|
||||
|
||||
func (a *AddrBook) AddAddress(addr *NetAddress, src *NetAddress) {
|
||||
a.mtx.Lock()
|
||||
defer a.mtx.Unlock()
|
||||
log.Info("Add address to book", "addr", addr, "src", src)
|
||||
a.addAddress(addr, src)
|
||||
}
|
||||
|
||||
func (a *AddrBook) NeedMoreAddrs() 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) *NetAddress {
|
||||
a.mtx.Lock()
|
||||
defer a.mtx.Unlock()
|
||||
|
||||
if a.size() == 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 map[string]*knownAddress = nil
|
||||
for len(bucket) == 0 {
|
||||
bucket = a.addrOld[a.rand.Intn(len(a.addrOld))]
|
||||
}
|
||||
// pick a random ka from bucket.
|
||||
randIndex := a.rand.Intn(len(bucket))
|
||||
for _, ka := range bucket {
|
||||
if randIndex == 0 {
|
||||
return ka.Addr
|
||||
}
|
||||
randIndex--
|
||||
}
|
||||
PanicSanity("Should not happen")
|
||||
} 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 {
|
||||
if randIndex == 0 {
|
||||
return ka.Addr
|
||||
}
|
||||
randIndex--
|
||||
}
|
||||
PanicSanity("Should not happen")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *AddrBook) MarkGood(addr *NetAddress) {
|
||||
a.mtx.Lock()
|
||||
defer a.mtx.Unlock()
|
||||
ka := a.addrLookup[addr.String()]
|
||||
if ka == nil {
|
||||
return
|
||||
}
|
||||
ka.markGood()
|
||||
if ka.isNew() {
|
||||
a.moveToOld(ka)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *AddrBook) MarkAttempt(addr *NetAddress) {
|
||||
a.mtx.Lock()
|
||||
defer a.mtx.Unlock()
|
||||
ka := a.addrLookup[addr.String()]
|
||||
if ka == nil {
|
||||
return
|
||||
}
|
||||
ka.markAttempt()
|
||||
}
|
||||
|
||||
func (a *AddrBook) MarkBad(addr *NetAddress) {
|
||||
a.mtx.Lock()
|
||||
defer a.mtx.Unlock()
|
||||
ka := a.addrLookup[addr.String()]
|
||||
if ka == nil {
|
||||
return
|
||||
}
|
||||
// We currently just eject the address.
|
||||
// In the future, consider blacklisting.
|
||||
a.removeFromAllBuckets(ka)
|
||||
}
|
||||
|
||||
/* 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.addrLookup {
|
||||
allAddr[i] = v.Addr
|
||||
i++
|
||||
}
|
||||
|
||||
numAddresses := MaxInt(
|
||||
MinInt(minGetSelection, len(allAddr)),
|
||||
len(allAddr)*getSelectionPercent/100)
|
||||
numAddresses = MinInt(maxGetSelection, numAddresses)
|
||||
|
||||
// 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 string
|
||||
Addrs []*knownAddress
|
||||
}
|
||||
|
||||
func (a *AddrBook) saveToFile(filePath string) {
|
||||
// Compile Addrs
|
||||
addrs := []*knownAddress{}
|
||||
for _, ka := range a.addrLookup {
|
||||
addrs = append(addrs, ka)
|
||||
}
|
||||
|
||||
aJSON := &addrBookJSON{
|
||||
Key: a.key,
|
||||
Addrs: addrs,
|
||||
}
|
||||
|
||||
jsonBytes, err := json.MarshalIndent(aJSON, "", "\t")
|
||||
if err != nil {
|
||||
log.Error("Failed to save AddrBook to file", "err", err)
|
||||
return
|
||||
}
|
||||
err = WriteFileAtomic(filePath, jsonBytes)
|
||||
if err != nil {
|
||||
log.Error("Failed to save AddrBook to file", "file", filePath, "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Returns false if file does not exist.
|
||||
// Panics if file is corrupt.
|
||||
func (a *AddrBook) loadFromFile(filePath string) bool {
|
||||
// If doesn't exist, do nothing.
|
||||
_, err := os.Stat(filePath)
|
||||
if os.IsNotExist(err) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Load addrBookJSON{}
|
||||
r, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
PanicCrisis(Fmt("Error opening file %s: %v", filePath, err))
|
||||
}
|
||||
defer r.Close()
|
||||
aJSON := &addrBookJSON{}
|
||||
dec := json.NewDecoder(r)
|
||||
err = dec.Decode(aJSON)
|
||||
if err != nil {
|
||||
PanicCrisis(Fmt("Error reading file %s: %v", filePath, err))
|
||||
}
|
||||
|
||||
// Restore all the fields...
|
||||
// Restore the key
|
||||
a.key = aJSON.Key
|
||||
// Restore .addrNew & .addrOld
|
||||
for _, ka := range aJSON.Addrs {
|
||||
for _, bucketIndex := range ka.Buckets {
|
||||
bucket := a.getBucket(ka.BucketType, bucketIndex)
|
||||
bucket[ka.Addr.String()] = ka
|
||||
}
|
||||
a.addrLookup[ka.Addr.String()] = ka
|
||||
if ka.BucketType == bucketTypeNew {
|
||||
a.nNew++
|
||||
} else {
|
||||
a.nOld++
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/* Private methods */
|
||||
|
||||
func (a *AddrBook) saveRoutine() {
|
||||
dumpAddressTicker := time.NewTicker(dumpAddressInterval)
|
||||
out:
|
||||
for {
|
||||
select {
|
||||
case <-dumpAddressTicker.C:
|
||||
log.Info("Saving AddrBook to file", "size", a.Size())
|
||||
a.saveToFile(a.filePath)
|
||||
case <-a.Quit:
|
||||
break out
|
||||
}
|
||||
}
|
||||
dumpAddressTicker.Stop()
|
||||
a.saveToFile(a.filePath)
|
||||
a.wg.Done()
|
||||
log.Notice("Address handler done")
|
||||
}
|
||||
|
||||
func (a *AddrBook) getBucket(bucketType byte, bucketIdx int) map[string]*knownAddress {
|
||||
switch bucketType {
|
||||
case bucketTypeNew:
|
||||
return a.addrNew[bucketIdx]
|
||||
case bucketTypeOld:
|
||||
return a.addrOld[bucketIdx]
|
||||
default:
|
||||
PanicSanity("Should not happen")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Adds ka to new bucket. Returns false if it couldn't do it cuz buckets full.
|
||||
// NOTE: currently it always returns true.
|
||||
func (a *AddrBook) addToNewBucket(ka *knownAddress, bucketIdx int) bool {
|
||||
// Sanity check
|
||||
if ka.isOld() {
|
||||
log.Warn(Fmt("Cannot add address already in old bucket to a new bucket: %v", ka))
|
||||
return false
|
||||
}
|
||||
|
||||
addrStr := ka.Addr.String()
|
||||
bucket := a.getBucket(bucketTypeNew, bucketIdx)
|
||||
|
||||
// Already exists?
|
||||
if _, ok := bucket[addrStr]; ok {
|
||||
return true
|
||||
}
|
||||
|
||||
// Enforce max addresses.
|
||||
if len(bucket) > newBucketSize {
|
||||
log.Notice("new bucket is full, expiring old ")
|
||||
a.expireNew(bucketIdx)
|
||||
}
|
||||
|
||||
// Add to bucket.
|
||||
bucket[addrStr] = ka
|
||||
if ka.addBucketRef(bucketIdx) == 1 {
|
||||
a.nNew++
|
||||
}
|
||||
|
||||
// Ensure in addrLookup
|
||||
a.addrLookup[addrStr] = ka
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Adds ka to old bucket. Returns false if it couldn't do it cuz buckets full.
|
||||
func (a *AddrBook) addToOldBucket(ka *knownAddress, bucketIdx int) bool {
|
||||
// Sanity check
|
||||
if ka.isNew() {
|
||||
log.Warn(Fmt("Cannot add new address to old bucket: %v", ka))
|
||||
return false
|
||||
}
|
||||
if len(ka.Buckets) != 0 {
|
||||
log.Warn(Fmt("Cannot add already old address to another old bucket: %v", ka))
|
||||
return false
|
||||
}
|
||||
|
||||
addrStr := ka.Addr.String()
|
||||
bucket := a.getBucket(bucketTypeNew, bucketIdx)
|
||||
|
||||
// Already exists?
|
||||
if _, ok := bucket[addrStr]; ok {
|
||||
return true
|
||||
}
|
||||
|
||||
// Enforce max addresses.
|
||||
if len(bucket) > oldBucketSize {
|
||||
return false
|
||||
}
|
||||
|
||||
// Add to bucket.
|
||||
bucket[addrStr] = ka
|
||||
if ka.addBucketRef(bucketIdx) == 1 {
|
||||
a.nOld++
|
||||
}
|
||||
|
||||
// Ensure in addrLookup
|
||||
a.addrLookup[addrStr] = ka
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (a *AddrBook) removeFromBucket(ka *knownAddress, bucketType byte, bucketIdx int) {
|
||||
if ka.BucketType != bucketType {
|
||||
log.Warn(Fmt("Bucket type mismatch: %v", ka))
|
||||
return
|
||||
}
|
||||
bucket := a.getBucket(bucketType, bucketIdx)
|
||||
delete(bucket, ka.Addr.String())
|
||||
if ka.removeBucketRef(bucketIdx) == 0 {
|
||||
if bucketType == bucketTypeNew {
|
||||
a.nNew--
|
||||
} else {
|
||||
a.nOld--
|
||||
}
|
||||
delete(a.addrLookup, ka.Addr.String())
|
||||
}
|
||||
}
|
||||
|
||||
func (a *AddrBook) removeFromAllBuckets(ka *knownAddress) {
|
||||
for _, bucketIdx := range ka.Buckets {
|
||||
bucket := a.getBucket(ka.BucketType, bucketIdx)
|
||||
delete(bucket, ka.Addr.String())
|
||||
}
|
||||
ka.Buckets = nil
|
||||
if ka.BucketType == bucketTypeNew {
|
||||
a.nNew--
|
||||
} else {
|
||||
a.nOld--
|
||||
}
|
||||
delete(a.addrLookup, ka.Addr.String())
|
||||
}
|
||||
|
||||
func (a *AddrBook) pickOldest(bucketType byte, bucketIdx int) *knownAddress {
|
||||
bucket := a.getBucket(bucketType, bucketIdx)
|
||||
var oldest *knownAddress
|
||||
for _, ka := range bucket {
|
||||
if oldest == nil || ka.LastAttempt.Before(oldest.LastAttempt) {
|
||||
oldest = ka
|
||||
}
|
||||
}
|
||||
return oldest
|
||||
}
|
||||
|
||||
func (a *AddrBook) addAddress(addr, src *NetAddress) {
|
||||
if !addr.Routable() {
|
||||
log.Warn(Fmt("Cannot add non-routable address %v", addr))
|
||||
return
|
||||
}
|
||||
if _, ok := a.ourAddrs[addr.String()]; ok {
|
||||
// Ignore our own listener address.
|
||||
return
|
||||
}
|
||||
|
||||
ka := a.addrLookup[addr.String()]
|
||||
|
||||
if ka != nil {
|
||||
// Already old.
|
||||
if ka.isOld() {
|
||||
return
|
||||
}
|
||||
// Already in max new buckets.
|
||||
if len(ka.Buckets) == maxNewBucketsPerAddress {
|
||||
return
|
||||
}
|
||||
// The more entries we have, the less likely we are to add more.
|
||||
factor := int32(2 * len(ka.Buckets))
|
||||
if a.rand.Int31n(factor) != 0 {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
ka = newKnownAddress(addr, src)
|
||||
}
|
||||
|
||||
bucket := a.calcNewBucket(addr, src)
|
||||
a.addToNewBucket(ka, bucket)
|
||||
|
||||
log.Notice("Added new address", "address", addr, "total", a.size())
|
||||
}
|
||||
|
||||
// Make space in the new buckets by expiring the really bad entries.
|
||||
// If no bad entries are available we remove the oldest.
|
||||
func (a *AddrBook) expireNew(bucketIdx int) {
|
||||
for addrStr, ka := range a.addrNew[bucketIdx] {
|
||||
// If an entry is bad, throw it away
|
||||
if ka.isBad() {
|
||||
log.Notice(Fmt("expiring bad address %v", addrStr))
|
||||
a.removeFromBucket(ka, bucketTypeNew, bucketIdx)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// If we haven't thrown out a bad entry, throw out the oldest entry
|
||||
oldest := a.pickOldest(bucketTypeNew, bucketIdx)
|
||||
a.removeFromBucket(oldest, bucketTypeNew, bucketIdx)
|
||||
}
|
||||
|
||||
// Promotes an address from new to old.
|
||||
// TODO: Move to old probabilistically.
|
||||
// The better a node is, the less likely it should be evicted from an old bucket.
|
||||
func (a *AddrBook) moveToOld(ka *knownAddress) {
|
||||
// Sanity check
|
||||
if ka.isOld() {
|
||||
log.Warn(Fmt("Cannot promote address that is already old %v", ka))
|
||||
return
|
||||
}
|
||||
if len(ka.Buckets) == 0 {
|
||||
log.Warn(Fmt("Cannot promote address that isn't in any new buckets %v", ka))
|
||||
return
|
||||
}
|
||||
|
||||
// Remember one of the buckets in which ka is in.
|
||||
freedBucket := ka.Buckets[0]
|
||||
// Remove from all (new) buckets.
|
||||
a.removeFromAllBuckets(ka)
|
||||
// It's officially old now.
|
||||
ka.BucketType = bucketTypeOld
|
||||
|
||||
// Try to add it to its oldBucket destination.
|
||||
oldBucketIdx := a.calcOldBucket(ka.Addr)
|
||||
added := a.addToOldBucket(ka, oldBucketIdx)
|
||||
if !added {
|
||||
// No room, must evict something
|
||||
oldest := a.pickOldest(bucketTypeOld, oldBucketIdx)
|
||||
a.removeFromBucket(oldest, bucketTypeOld, oldBucketIdx)
|
||||
// Find new bucket to put oldest in
|
||||
newBucketIdx := a.calcNewBucket(oldest.Addr, oldest.Src)
|
||||
added := a.addToNewBucket(oldest, newBucketIdx)
|
||||
// No space in newBucket either, just put it in freedBucket from above.
|
||||
if !added {
|
||||
added := a.addToNewBucket(oldest, freedBucket)
|
||||
if !added {
|
||||
log.Warn(Fmt("Could not migrate oldest %v to freedBucket %v", oldest, freedBucket))
|
||||
}
|
||||
}
|
||||
// Finally, add to bucket again.
|
||||
added = a.addToOldBucket(ka, oldBucketIdx)
|
||||
if !added {
|
||||
log.Warn(Fmt("Could not re-add ka %v to oldBucketIdx %v", ka, oldBucketIdx))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// doublesha256( key + sourcegroup +
|
||||
// int64(doublesha256(key + group + sourcegroup))%bucket_per_group ) % num_new_buckets
|
||||
func (a *AddrBook) calcNewBucket(addr, src *NetAddress) int {
|
||||
data1 := []byte{}
|
||||
data1 = append(data1, []byte(a.key)...)
|
||||
data1 = append(data1, []byte(groupKey(addr))...)
|
||||
data1 = append(data1, []byte(groupKey(src))...)
|
||||
hash1 := doubleSha256(data1)
|
||||
hash64 := binary.BigEndian.Uint64(hash1)
|
||||
hash64 %= newBucketsPerGroup
|
||||
var hashbuf [8]byte
|
||||
binary.BigEndian.PutUint64(hashbuf[:], hash64)
|
||||
data2 := []byte{}
|
||||
data2 = append(data2, []byte(a.key)...)
|
||||
data2 = append(data2, groupKey(src)...)
|
||||
data2 = append(data2, hashbuf[:]...)
|
||||
|
||||
hash2 := doubleSha256(data2)
|
||||
return int(binary.BigEndian.Uint64(hash2) % newBucketCount)
|
||||
}
|
||||
|
||||
// doublesha256( key + group +
|
||||
// int64(doublesha256(key + addr))%buckets_per_group ) % num_old_buckets
|
||||
func (a *AddrBook) calcOldBucket(addr *NetAddress) int {
|
||||
data1 := []byte{}
|
||||
data1 = append(data1, []byte(a.key)...)
|
||||
data1 = append(data1, []byte(addr.String())...)
|
||||
hash1 := doubleSha256(data1)
|
||||
hash64 := binary.BigEndian.Uint64(hash1)
|
||||
hash64 %= oldBucketsPerGroup
|
||||
var hashbuf [8]byte
|
||||
binary.BigEndian.PutUint64(hashbuf[:], hash64)
|
||||
data2 := []byte{}
|
||||
data2 = append(data2, []byte(a.key)...)
|
||||
data2 = append(data2, groupKey(addr)...)
|
||||
data2 = append(data2, hashbuf[:]...)
|
||||
|
||||
hash2 := doubleSha256(data2)
|
||||
return int(binary.BigEndian.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 int32
|
||||
LastAttempt time.Time
|
||||
LastSuccess time.Time
|
||||
BucketType byte
|
||||
Buckets []int
|
||||
}
|
||||
|
||||
func newKnownAddress(addr *NetAddress, src *NetAddress) *knownAddress {
|
||||
return &knownAddress{
|
||||
Addr: addr,
|
||||
Src: src,
|
||||
Attempts: 0,
|
||||
LastAttempt: time.Now(),
|
||||
BucketType: bucketTypeNew,
|
||||
Buckets: nil,
|
||||
}
|
||||
}
|
||||
|
||||
func (ka *knownAddress) isOld() bool {
|
||||
return ka.BucketType == bucketTypeOld
|
||||
}
|
||||
|
||||
func (ka *knownAddress) isNew() bool {
|
||||
return ka.BucketType == bucketTypeNew
|
||||
}
|
||||
|
||||
func (ka *knownAddress) markAttempt() {
|
||||
now := time.Now()
|
||||
ka.LastAttempt = now
|
||||
ka.Attempts += 1
|
||||
}
|
||||
|
||||
func (ka *knownAddress) markGood() {
|
||||
now := time.Now()
|
||||
ka.LastAttempt = now
|
||||
ka.Attempts = 0
|
||||
ka.LastSuccess = now
|
||||
}
|
||||
|
||||
func (ka *knownAddress) addBucketRef(bucketIdx int) int {
|
||||
for _, bucket := range ka.Buckets {
|
||||
if bucket == bucketIdx {
|
||||
log.Warn(Fmt("Bucket already exists in ka.Buckets: %v", ka))
|
||||
return -1
|
||||
}
|
||||
}
|
||||
ka.Buckets = append(ka.Buckets, bucketIdx)
|
||||
return len(ka.Buckets)
|
||||
}
|
||||
|
||||
func (ka *knownAddress) removeBucketRef(bucketIdx int) int {
|
||||
buckets := []int{}
|
||||
for _, bucket := range ka.Buckets {
|
||||
if bucket != bucketIdx {
|
||||
buckets = append(buckets, bucket)
|
||||
}
|
||||
}
|
||||
if len(buckets) != len(ka.Buckets)-1 {
|
||||
log.Warn(Fmt("bucketIdx not found in ka.Buckets: %v", ka))
|
||||
return -1
|
||||
}
|
||||
ka.Buckets = buckets
|
||||
return len(ka.Buckets)
|
||||
}
|
||||
|
||||
/*
|
||||
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
|
||||
}
|
||||
@@ -1,161 +0,0 @@
|
||||
package p2p
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func createTempFileName(prefix string) string {
|
||||
f, err := ioutil.TempFile("", prefix)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fname := f.Name()
|
||||
err = f.Close()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return fname
|
||||
}
|
||||
|
||||
func TestEmpty(t *testing.T) {
|
||||
fname := createTempFileName("addrbook_test")
|
||||
// t.Logf("New tempfile name: %v", fname)
|
||||
|
||||
// Save an empty book & load it
|
||||
book := NewAddrBook(fname)
|
||||
book.saveToFile(fname)
|
||||
|
||||
book = NewAddrBook(fname)
|
||||
book.loadFromFile(fname)
|
||||
|
||||
if book.Size() != 0 {
|
||||
t.Errorf("Expected 0 addresses, found %v", book.Size())
|
||||
}
|
||||
}
|
||||
|
||||
func randIPv4Address() *NetAddress {
|
||||
for {
|
||||
ip := fmt.Sprintf("%v.%v.%v.%v",
|
||||
rand.Intn(254)+1,
|
||||
rand.Intn(255),
|
||||
rand.Intn(255),
|
||||
rand.Intn(255),
|
||||
)
|
||||
port := rand.Intn(65535-1) + 1
|
||||
addr := NewNetAddressString(fmt.Sprintf("%v:%v", ip, port))
|
||||
if addr.Routable() {
|
||||
return addr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSaveAddresses(t *testing.T) {
|
||||
fname := createTempFileName("addrbook_test")
|
||||
//t.Logf("New tempfile name: %v", fname)
|
||||
|
||||
// Create some random addresses
|
||||
randAddrs := []struct {
|
||||
addr *NetAddress
|
||||
src *NetAddress
|
||||
}{}
|
||||
for i := 0; i < 100; i++ {
|
||||
addr := randIPv4Address()
|
||||
src := randIPv4Address()
|
||||
randAddrs = append(randAddrs, struct {
|
||||
addr *NetAddress
|
||||
src *NetAddress
|
||||
}{
|
||||
addr: addr,
|
||||
src: src,
|
||||
})
|
||||
}
|
||||
|
||||
// Create the book & populate & save
|
||||
book := NewAddrBook(fname)
|
||||
for _, addrSrc := range randAddrs {
|
||||
book.AddAddress(addrSrc.addr, addrSrc.src)
|
||||
}
|
||||
if book.Size() != 100 {
|
||||
t.Errorf("Expected 100 addresses, found %v", book.Size())
|
||||
}
|
||||
book.saveToFile(fname)
|
||||
|
||||
// Reload the book
|
||||
book = NewAddrBook(fname)
|
||||
book.loadFromFile(fname)
|
||||
|
||||
// Test ...
|
||||
|
||||
if book.Size() != 100 {
|
||||
t.Errorf("Expected 100 addresses, found %v", book.Size())
|
||||
}
|
||||
|
||||
for _, addrSrc := range randAddrs {
|
||||
addr := addrSrc.addr
|
||||
src := addrSrc.src
|
||||
ka := book.addrLookup[addr.String()]
|
||||
if ka == nil {
|
||||
t.Fatalf("Expected to find KnownAddress %v but wasn't there.", addr)
|
||||
}
|
||||
if !(ka.Addr.Equals(addr) && ka.Src.Equals(src)) {
|
||||
t.Fatalf("KnownAddress doesn't match addr & src")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPromoteToOld(t *testing.T) {
|
||||
fname := createTempFileName("addrbook_test")
|
||||
t.Logf("New tempfile name: %v", fname)
|
||||
|
||||
// Create some random addresses
|
||||
randAddrs := []struct {
|
||||
addr *NetAddress
|
||||
src *NetAddress
|
||||
}{}
|
||||
for i := 0; i < 100; i++ {
|
||||
addr := randIPv4Address()
|
||||
src := randIPv4Address()
|
||||
randAddrs = append(randAddrs, struct {
|
||||
addr *NetAddress
|
||||
src *NetAddress
|
||||
}{
|
||||
addr: addr,
|
||||
src: src,
|
||||
})
|
||||
}
|
||||
|
||||
// Create the book & populate & save
|
||||
book := NewAddrBook(fname)
|
||||
for _, addrSrc := range randAddrs {
|
||||
book.AddAddress(addrSrc.addr, addrSrc.src)
|
||||
}
|
||||
// Attempt all addresses.
|
||||
for _, addrSrc := range randAddrs {
|
||||
book.MarkAttempt(addrSrc.addr)
|
||||
}
|
||||
// Promote half of them
|
||||
for i, addrSrc := range randAddrs {
|
||||
if i%2 == 0 {
|
||||
book.MarkGood(addrSrc.addr)
|
||||
}
|
||||
}
|
||||
book.saveToFile(fname)
|
||||
|
||||
// Reload the book
|
||||
book = NewAddrBook(fname)
|
||||
book.loadFromFile(fname)
|
||||
|
||||
// Test ...
|
||||
|
||||
if book.Size() != 100 {
|
||||
t.Errorf("Expected 100 addresses, found %v", book.Size())
|
||||
}
|
||||
|
||||
// TODO: do more testing :)
|
||||
|
||||
selection := book.GetSelection()
|
||||
t.Logf("selection: %v", selection)
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package p2p
|
||||
|
||||
import (
|
||||
cfg "github.com/tendermint/tendermint/config"
|
||||
)
|
||||
|
||||
var config cfg.Config = nil
|
||||
|
||||
func init() {
|
||||
cfg.OnConfig(func(newConfig cfg.Config) {
|
||||
config = newConfig
|
||||
})
|
||||
}
|
||||
@@ -1,640 +0,0 @@
|
||||
package p2p
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"net"
|
||||
"runtime/debug"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
flow "github.com/tendermint/tendermint/Godeps/_workspace/src/code.google.com/p/mxk/go1/flowcontrol"
|
||||
. "github.com/tendermint/tendermint/common"
|
||||
"github.com/tendermint/tendermint/wire" //"github.com/tendermint/log15"
|
||||
)
|
||||
|
||||
const (
|
||||
numBatchMsgPackets = 10
|
||||
minReadBufferSize = 1024
|
||||
minWriteBufferSize = 1024
|
||||
idleTimeoutMinutes = 5
|
||||
updateStatsSeconds = 2
|
||||
pingTimeoutSeconds = 40
|
||||
defaultSendRate = 51200 // 50Kb/s
|
||||
defaultRecvRate = 51200 // 50Kb/s
|
||||
flushThrottleMS = 100
|
||||
defaultSendQueueCapacity = 1
|
||||
defaultRecvBufferCapacity = 4096
|
||||
defaultSendTimeoutSeconds = 10
|
||||
)
|
||||
|
||||
type receiveCbFunc func(chID byte, msgBytes []byte)
|
||||
type errorCbFunc func(interface{})
|
||||
|
||||
/*
|
||||
Each peer has one `MConnection` (multiplex connection) instance.
|
||||
|
||||
__multiplex__ *noun* a system or signal involving simultaneous transmission of
|
||||
several messages along a single channel of communication.
|
||||
|
||||
Each `MConnection` handles message transmission on multiple abstract communication
|
||||
`Channel`s. Each channel has a globally unique byte id.
|
||||
The byte id and the relative priorities of each `Channel` are configured upon
|
||||
initialization of the connection.
|
||||
|
||||
There are two methods for sending messages:
|
||||
func (m MConnection) Send(chID byte, msg interface{}) bool {}
|
||||
func (m MConnection) TrySend(chID byte, msg interface{}) bool {}
|
||||
|
||||
`Send(chID, msg)` is a blocking call that waits until `msg` is successfully queued
|
||||
for the channel with the given id byte `chID`, or until the request times out.
|
||||
The message `msg` is serialized using the `tendermint/wire` submodule's
|
||||
`WriteBinary()` reflection routine.
|
||||
|
||||
`TrySend(chID, msg)` is a nonblocking call that returns false if the channel's
|
||||
queue is full.
|
||||
|
||||
Inbound message bytes are handled with an onReceive callback function.
|
||||
*/
|
||||
type MConnection struct {
|
||||
BaseService
|
||||
|
||||
conn net.Conn
|
||||
bufReader *bufio.Reader
|
||||
bufWriter *bufio.Writer
|
||||
sendMonitor *flow.Monitor
|
||||
recvMonitor *flow.Monitor
|
||||
sendRate int64
|
||||
recvRate int64
|
||||
send chan struct{}
|
||||
pong chan struct{}
|
||||
channels []*Channel
|
||||
channelsIdx map[byte]*Channel
|
||||
onReceive receiveCbFunc
|
||||
onError errorCbFunc
|
||||
errored uint32
|
||||
|
||||
quit chan struct{}
|
||||
flushTimer *ThrottleTimer // flush writes as necessary but throttled.
|
||||
pingTimer *RepeatTimer // send pings periodically
|
||||
chStatsTimer *RepeatTimer // update channel stats periodically
|
||||
|
||||
LocalAddress *NetAddress
|
||||
RemoteAddress *NetAddress
|
||||
}
|
||||
|
||||
func NewMConnection(conn net.Conn, chDescs []*ChannelDescriptor, onReceive receiveCbFunc, onError errorCbFunc) *MConnection {
|
||||
|
||||
mconn := &MConnection{
|
||||
conn: conn,
|
||||
bufReader: bufio.NewReaderSize(conn, minReadBufferSize),
|
||||
bufWriter: bufio.NewWriterSize(conn, minWriteBufferSize),
|
||||
sendMonitor: flow.New(0, 0),
|
||||
recvMonitor: flow.New(0, 0),
|
||||
sendRate: defaultSendRate,
|
||||
recvRate: defaultRecvRate,
|
||||
send: make(chan struct{}, 1),
|
||||
pong: make(chan struct{}),
|
||||
onReceive: onReceive,
|
||||
onError: onError,
|
||||
|
||||
// Initialized in Start()
|
||||
quit: nil,
|
||||
flushTimer: nil,
|
||||
pingTimer: nil,
|
||||
chStatsTimer: nil,
|
||||
|
||||
LocalAddress: NewNetAddress(conn.LocalAddr()),
|
||||
RemoteAddress: NewNetAddress(conn.RemoteAddr()),
|
||||
}
|
||||
|
||||
// Create channels
|
||||
var channelsIdx = map[byte]*Channel{}
|
||||
var channels = []*Channel{}
|
||||
|
||||
for _, desc := range chDescs {
|
||||
channel := newChannel(mconn, desc)
|
||||
channelsIdx[channel.id] = channel
|
||||
channels = append(channels, channel)
|
||||
}
|
||||
mconn.channels = channels
|
||||
mconn.channelsIdx = channelsIdx
|
||||
|
||||
mconn.BaseService = *NewBaseService(log, "MConnection", mconn)
|
||||
|
||||
return mconn
|
||||
}
|
||||
|
||||
func (c *MConnection) OnStart() error {
|
||||
c.BaseService.OnStart()
|
||||
c.quit = make(chan struct{})
|
||||
c.flushTimer = NewThrottleTimer("flush", flushThrottleMS*time.Millisecond)
|
||||
c.pingTimer = NewRepeatTimer("ping", pingTimeoutSeconds*time.Second)
|
||||
c.chStatsTimer = NewRepeatTimer("chStats", updateStatsSeconds*time.Second)
|
||||
go c.sendRoutine()
|
||||
go c.recvRoutine()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *MConnection) OnStop() {
|
||||
c.BaseService.OnStop()
|
||||
c.flushTimer.Stop()
|
||||
c.pingTimer.Stop()
|
||||
c.chStatsTimer.Stop()
|
||||
if c.quit != nil {
|
||||
close(c.quit)
|
||||
}
|
||||
c.conn.Close()
|
||||
// We can't close pong safely here because
|
||||
// recvRoutine may write to it after we've stopped.
|
||||
// Though it doesn't need to get closed at all,
|
||||
// we close it @ recvRoutine.
|
||||
// close(c.pong)
|
||||
}
|
||||
|
||||
func (c *MConnection) String() string {
|
||||
return fmt.Sprintf("MConn{%v}", c.conn.RemoteAddr())
|
||||
}
|
||||
|
||||
func (c *MConnection) flush() {
|
||||
log.Debug("Flush", "conn", c)
|
||||
err := c.bufWriter.Flush()
|
||||
if err != nil {
|
||||
log.Warn("MConnection flush failed", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Catch panics, usually caused by remote disconnects.
|
||||
func (c *MConnection) _recover() {
|
||||
if r := recover(); r != nil {
|
||||
stack := debug.Stack()
|
||||
err := StackError{r, stack}
|
||||
c.stopForError(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *MConnection) stopForError(r interface{}) {
|
||||
c.Stop()
|
||||
if atomic.CompareAndSwapUint32(&c.errored, 0, 1) {
|
||||
if c.onError != nil {
|
||||
c.onError(r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Queues a message to be sent to channel.
|
||||
func (c *MConnection) Send(chID byte, msg interface{}) bool {
|
||||
if !c.IsRunning() {
|
||||
return false
|
||||
}
|
||||
|
||||
log.Info("Send", "channel", chID, "conn", c, "msg", msg) //, "bytes", wire.BinaryBytes(msg))
|
||||
|
||||
// Send message to channel.
|
||||
channel, ok := c.channelsIdx[chID]
|
||||
if !ok {
|
||||
log.Error(Fmt("Cannot send bytes, unknown channel %X", chID))
|
||||
return false
|
||||
}
|
||||
|
||||
success := channel.sendBytes(wire.BinaryBytes(msg))
|
||||
if success {
|
||||
// Wake up sendRoutine if necessary
|
||||
select {
|
||||
case c.send <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
} else {
|
||||
log.Warn("Send failed", "channel", chID, "conn", c, "msg", msg)
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
// Queues a message to be sent to channel.
|
||||
// Nonblocking, returns true if successful.
|
||||
func (c *MConnection) TrySend(chID byte, msg interface{}) bool {
|
||||
if !c.IsRunning() {
|
||||
return false
|
||||
}
|
||||
|
||||
log.Info("TrySend", "channel", chID, "conn", c, "msg", msg)
|
||||
|
||||
// Send message to channel.
|
||||
channel, ok := c.channelsIdx[chID]
|
||||
if !ok {
|
||||
log.Error(Fmt("Cannot send bytes, unknown channel %X", chID))
|
||||
return false
|
||||
}
|
||||
|
||||
ok = channel.trySendBytes(wire.BinaryBytes(msg))
|
||||
if ok {
|
||||
// Wake up sendRoutine if necessary
|
||||
select {
|
||||
case c.send <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
return ok
|
||||
}
|
||||
|
||||
func (c *MConnection) CanSend(chID byte) bool {
|
||||
if !c.IsRunning() {
|
||||
return false
|
||||
}
|
||||
|
||||
channel, ok := c.channelsIdx[chID]
|
||||
if !ok {
|
||||
log.Error(Fmt("Unknown channel %X", chID))
|
||||
return false
|
||||
}
|
||||
return channel.canSend()
|
||||
}
|
||||
|
||||
// sendRoutine polls for packets to send from channels.
|
||||
func (c *MConnection) sendRoutine() {
|
||||
defer c._recover()
|
||||
|
||||
FOR_LOOP:
|
||||
for {
|
||||
var n int64
|
||||
var err error
|
||||
select {
|
||||
case <-c.flushTimer.Ch:
|
||||
// NOTE: flushTimer.Set() must be called every time
|
||||
// something is written to .bufWriter.
|
||||
c.flush()
|
||||
case <-c.chStatsTimer.Ch:
|
||||
for _, channel := range c.channels {
|
||||
channel.updateStats()
|
||||
}
|
||||
case <-c.pingTimer.Ch:
|
||||
log.Info("Send Ping")
|
||||
wire.WriteByte(packetTypePing, c.bufWriter, &n, &err)
|
||||
c.sendMonitor.Update(int(n))
|
||||
c.flush()
|
||||
case <-c.pong:
|
||||
log.Info("Send Pong")
|
||||
wire.WriteByte(packetTypePong, c.bufWriter, &n, &err)
|
||||
c.sendMonitor.Update(int(n))
|
||||
c.flush()
|
||||
case <-c.quit:
|
||||
break FOR_LOOP
|
||||
case <-c.send:
|
||||
// Send some msgPackets
|
||||
eof := c.sendSomeMsgPackets()
|
||||
if !eof {
|
||||
// Keep sendRoutine awake.
|
||||
select {
|
||||
case c.send <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !c.IsRunning() {
|
||||
break FOR_LOOP
|
||||
}
|
||||
if err != nil {
|
||||
log.Warn("Connection failed @ sendRoutine", "conn", c, "error", err)
|
||||
c.stopForError(err)
|
||||
break FOR_LOOP
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
}
|
||||
|
||||
// Returns true if messages from channels were exhausted.
|
||||
// Blocks in accordance to .sendMonitor throttling.
|
||||
func (c *MConnection) sendSomeMsgPackets() bool {
|
||||
// Block until .sendMonitor says we can write.
|
||||
// Once we're ready we send more than we asked for,
|
||||
// but amortized it should even out.
|
||||
c.sendMonitor.Limit(maxMsgPacketSize, atomic.LoadInt64(&c.sendRate), true)
|
||||
|
||||
// Now send some msgPackets.
|
||||
for i := 0; i < numBatchMsgPackets; i++ {
|
||||
if c.sendMsgPacket() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Returns true if messages from channels were exhausted.
|
||||
func (c *MConnection) sendMsgPacket() bool {
|
||||
// Choose a channel to create a msgPacket from.
|
||||
// The chosen channel will be the one whose recentlySent/priority is the least.
|
||||
var leastRatio float32 = math.MaxFloat32
|
||||
var leastChannel *Channel
|
||||
for _, channel := range c.channels {
|
||||
// If nothing to send, skip this channel
|
||||
if !channel.isSendPending() {
|
||||
continue
|
||||
}
|
||||
// Get ratio, and keep track of lowest ratio.
|
||||
ratio := float32(channel.recentlySent) / float32(channel.priority)
|
||||
if ratio < leastRatio {
|
||||
leastRatio = ratio
|
||||
leastChannel = channel
|
||||
}
|
||||
}
|
||||
|
||||
// Nothing to send?
|
||||
if leastChannel == nil {
|
||||
return true
|
||||
} else {
|
||||
// log.Info("Found a msgPacket to send")
|
||||
}
|
||||
|
||||
// Make & send a msgPacket from this channel
|
||||
n, err := leastChannel.writeMsgPacketTo(c.bufWriter)
|
||||
if err != nil {
|
||||
log.Warn("Failed to write msgPacket", "error", err)
|
||||
c.stopForError(err)
|
||||
return true
|
||||
}
|
||||
c.sendMonitor.Update(int(n))
|
||||
c.flushTimer.Set()
|
||||
return false
|
||||
}
|
||||
|
||||
// recvRoutine reads msgPackets and reconstructs the message using the channels' "recving" buffer.
|
||||
// After a whole message has been assembled, it's pushed to onReceive().
|
||||
// Blocks depending on how the connection is throttled.
|
||||
func (c *MConnection) recvRoutine() {
|
||||
defer c._recover()
|
||||
|
||||
FOR_LOOP:
|
||||
for {
|
||||
// Block until .recvMonitor says we can read.
|
||||
c.recvMonitor.Limit(maxMsgPacketSize, atomic.LoadInt64(&c.recvRate), true)
|
||||
|
||||
/*
|
||||
// Peek into bufReader for debugging
|
||||
if numBytes := c.bufReader.Buffered(); numBytes > 0 {
|
||||
log.Info("Peek connection buffer", "numBytes", numBytes, "bytes", log15.Lazy{func() []byte {
|
||||
bytes, err := c.bufReader.Peek(MinInt(numBytes, 100))
|
||||
if err == nil {
|
||||
return bytes
|
||||
} else {
|
||||
log.Warn("Error peeking connection buffer", "error", err)
|
||||
return nil
|
||||
}
|
||||
}})
|
||||
}
|
||||
*/
|
||||
|
||||
// Read packet type
|
||||
var n int64
|
||||
var err error
|
||||
pktType := wire.ReadByte(c.bufReader, &n, &err)
|
||||
c.recvMonitor.Update(int(n))
|
||||
if err != nil {
|
||||
if c.IsRunning() {
|
||||
log.Warn("Connection failed @ recvRoutine (reading byte)", "conn", c, "error", err)
|
||||
c.stopForError(err)
|
||||
}
|
||||
break FOR_LOOP
|
||||
}
|
||||
|
||||
// Read more depending on packet type.
|
||||
switch pktType {
|
||||
case packetTypePing:
|
||||
// TODO: prevent abuse, as they cause flush()'s.
|
||||
log.Info("Receive Ping")
|
||||
c.pong <- struct{}{}
|
||||
case packetTypePong:
|
||||
// do nothing
|
||||
log.Info("Receive Pong")
|
||||
case packetTypeMsg:
|
||||
pkt, n, err := msgPacket{}, int64(0), error(nil)
|
||||
wire.ReadBinaryPtr(&pkt, c.bufReader, &n, &err)
|
||||
c.recvMonitor.Update(int(n))
|
||||
if err != nil {
|
||||
if c.IsRunning() {
|
||||
log.Warn("Connection failed @ recvRoutine", "conn", c, "error", err)
|
||||
c.stopForError(err)
|
||||
}
|
||||
break FOR_LOOP
|
||||
}
|
||||
channel, ok := c.channelsIdx[pkt.ChannelID]
|
||||
if !ok || channel == nil {
|
||||
PanicQ(Fmt("Unknown channel %X", pkt.ChannelID))
|
||||
}
|
||||
msgBytes, err := channel.recvMsgPacket(pkt)
|
||||
if err != nil {
|
||||
if c.IsRunning() {
|
||||
log.Warn("Connection failed @ recvRoutine", "conn", c, "error", err)
|
||||
c.stopForError(err)
|
||||
}
|
||||
break FOR_LOOP
|
||||
}
|
||||
if msgBytes != nil {
|
||||
log.Debug("Received bytes", "chID", pkt.ChannelID, "msgBytes", msgBytes)
|
||||
c.onReceive(pkt.ChannelID, msgBytes)
|
||||
}
|
||||
default:
|
||||
PanicSanity(Fmt("Unknown message type %X", pktType))
|
||||
}
|
||||
|
||||
// TODO: shouldn't this go in the sendRoutine?
|
||||
// Better to send a ping packet when *we* haven't sent anything for a while.
|
||||
c.pingTimer.Reset()
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
close(c.pong)
|
||||
for _ = range c.pong {
|
||||
// Drain
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
type ChannelDescriptor struct {
|
||||
ID byte
|
||||
Priority int
|
||||
SendQueueCapacity int
|
||||
RecvBufferCapacity int
|
||||
}
|
||||
|
||||
func (chDesc *ChannelDescriptor) FillDefaults() {
|
||||
if chDesc.SendQueueCapacity == 0 {
|
||||
chDesc.SendQueueCapacity = defaultSendQueueCapacity
|
||||
}
|
||||
if chDesc.RecvBufferCapacity == 0 {
|
||||
chDesc.RecvBufferCapacity = defaultRecvBufferCapacity
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: lowercase.
|
||||
// NOTE: not goroutine-safe.
|
||||
type Channel struct {
|
||||
conn *MConnection
|
||||
desc *ChannelDescriptor
|
||||
id byte
|
||||
sendQueue chan []byte
|
||||
sendQueueSize int32 // atomic.
|
||||
recving []byte
|
||||
sending []byte
|
||||
priority int
|
||||
recentlySent int64 // exponential moving average
|
||||
}
|
||||
|
||||
func newChannel(conn *MConnection, desc *ChannelDescriptor) *Channel {
|
||||
desc.FillDefaults()
|
||||
if desc.Priority <= 0 {
|
||||
PanicSanity("Channel default priority must be a postive integer")
|
||||
}
|
||||
return &Channel{
|
||||
conn: conn,
|
||||
desc: desc,
|
||||
id: desc.ID,
|
||||
sendQueue: make(chan []byte, desc.SendQueueCapacity),
|
||||
recving: make([]byte, 0, desc.RecvBufferCapacity),
|
||||
priority: desc.Priority,
|
||||
}
|
||||
}
|
||||
|
||||
// Queues message to send to this channel.
|
||||
// Goroutine-safe
|
||||
// Times out (and returns false) after defaultSendTimeoutSeconds
|
||||
func (ch *Channel) sendBytes(bytes []byte) bool {
|
||||
timeout := time.NewTimer(defaultSendTimeoutSeconds * time.Second)
|
||||
select {
|
||||
case <-timeout.C:
|
||||
// timeout
|
||||
return false
|
||||
case ch.sendQueue <- bytes:
|
||||
atomic.AddInt32(&ch.sendQueueSize, 1)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Queues message to send to this channel.
|
||||
// Nonblocking, returns true if successful.
|
||||
// Goroutine-safe
|
||||
func (ch *Channel) trySendBytes(bytes []byte) bool {
|
||||
select {
|
||||
case ch.sendQueue <- bytes:
|
||||
atomic.AddInt32(&ch.sendQueueSize, 1)
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Goroutine-safe
|
||||
func (ch *Channel) loadSendQueueSize() (size int) {
|
||||
return int(atomic.LoadInt32(&ch.sendQueueSize))
|
||||
}
|
||||
|
||||
// Goroutine-safe
|
||||
// Use only as a heuristic.
|
||||
func (ch *Channel) canSend() bool {
|
||||
return ch.loadSendQueueSize() < defaultSendQueueCapacity
|
||||
}
|
||||
|
||||
// Returns true if any msgPackets are pending to be sent.
|
||||
// Call before calling nextMsgPacket()
|
||||
// Goroutine-safe
|
||||
func (ch *Channel) isSendPending() bool {
|
||||
if len(ch.sending) == 0 {
|
||||
if len(ch.sendQueue) == 0 {
|
||||
return false
|
||||
}
|
||||
ch.sending = <-ch.sendQueue
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Creates a new msgPacket to send.
|
||||
// Not goroutine-safe
|
||||
func (ch *Channel) nextMsgPacket() msgPacket {
|
||||
packet := msgPacket{}
|
||||
packet.ChannelID = byte(ch.id)
|
||||
packet.Bytes = ch.sending[:MinInt(maxMsgPacketSize, len(ch.sending))]
|
||||
if len(ch.sending) <= maxMsgPacketSize {
|
||||
packet.EOF = byte(0x01)
|
||||
ch.sending = nil
|
||||
atomic.AddInt32(&ch.sendQueueSize, -1) // decrement sendQueueSize
|
||||
} else {
|
||||
packet.EOF = byte(0x00)
|
||||
ch.sending = ch.sending[MinInt(maxMsgPacketSize, len(ch.sending)):]
|
||||
}
|
||||
return packet
|
||||
}
|
||||
|
||||
// Writes next msgPacket to w.
|
||||
// Not goroutine-safe
|
||||
func (ch *Channel) writeMsgPacketTo(w io.Writer) (n int64, err error) {
|
||||
packet := ch.nextMsgPacket()
|
||||
log.Debug("Write Msg Packet", "conn", ch.conn, "packet", packet)
|
||||
wire.WriteByte(packetTypeMsg, w, &n, &err)
|
||||
wire.WriteBinary(packet, w, &n, &err)
|
||||
if err != nil {
|
||||
ch.recentlySent += n
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Handles incoming msgPackets. Returns a msg bytes if msg is complete.
|
||||
// Not goroutine-safe
|
||||
func (ch *Channel) recvMsgPacket(packet msgPacket) ([]byte, error) {
|
||||
// log.Debug("Read Msg Packet", "conn", ch.conn, "packet", packet)
|
||||
if wire.MaxBinaryReadSize < len(ch.recving)+len(packet.Bytes) {
|
||||
return nil, wire.ErrBinaryReadSizeOverflow
|
||||
}
|
||||
ch.recving = append(ch.recving, packet.Bytes...)
|
||||
if packet.EOF == byte(0x01) {
|
||||
msgBytes := ch.recving
|
||||
ch.recving = make([]byte, 0, defaultRecvBufferCapacity)
|
||||
return msgBytes, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Call this periodically to update stats for throttling purposes.
|
||||
// Not goroutine-safe
|
||||
func (ch *Channel) updateStats() {
|
||||
// Exponential decay of stats.
|
||||
// TODO: optimize.
|
||||
ch.recentlySent = int64(float64(ch.recentlySent) * 0.5)
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
const (
|
||||
maxMsgPacketSize = 1024
|
||||
packetTypePing = byte(0x01)
|
||||
packetTypePong = byte(0x02)
|
||||
packetTypeMsg = byte(0x03)
|
||||
)
|
||||
|
||||
// Messages in channels are chopped into smaller msgPackets for multiplexing.
|
||||
type msgPacket struct {
|
||||
ChannelID byte
|
||||
EOF byte // 1 means message ends here.
|
||||
Bytes []byte
|
||||
}
|
||||
|
||||
func (p msgPacket) String() string {
|
||||
return fmt.Sprintf("MsgPacket{%X:%X T:%X}", p.ChannelID, p.Bytes, p.EOF)
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// Convenience struct for writing typed messages.
|
||||
// Reading requires a custom decoder that switches on the first type byte of a byteslice.
|
||||
type TypedMessage struct {
|
||||
Type byte
|
||||
Msg interface{}
|
||||
}
|
||||
|
||||
func (tm TypedMessage) String() string {
|
||||
return fmt.Sprintf("TMsg{%X:%v}", tm.Type, tm.Msg)
|
||||
}
|
||||
-212
@@ -1,212 +0,0 @@
|
||||
package p2p
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
. "github.com/tendermint/tendermint/common"
|
||||
"github.com/tendermint/tendermint/p2p/upnp"
|
||||
)
|
||||
|
||||
type Listener interface {
|
||||
Connections() <-chan net.Conn
|
||||
InternalAddress() *NetAddress
|
||||
ExternalAddress() *NetAddress
|
||||
String() string
|
||||
Stop() bool
|
||||
}
|
||||
|
||||
// Implements Listener
|
||||
type DefaultListener struct {
|
||||
BaseService
|
||||
|
||||
listener net.Listener
|
||||
intAddr *NetAddress
|
||||
extAddr *NetAddress
|
||||
connections chan net.Conn
|
||||
}
|
||||
|
||||
const (
|
||||
numBufferedConnections = 10
|
||||
defaultExternalPort = 8770
|
||||
tryListenSeconds = 5
|
||||
)
|
||||
|
||||
func splitHostPort(addr string) (host string, port int) {
|
||||
host, portStr, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
PanicSanity(err)
|
||||
}
|
||||
port, err = strconv.Atoi(portStr)
|
||||
if err != nil {
|
||||
PanicSanity(err)
|
||||
}
|
||||
return host, port
|
||||
}
|
||||
|
||||
func NewDefaultListener(protocol string, lAddr string) Listener {
|
||||
// Local listen IP & port
|
||||
lAddrIP, lAddrPort := splitHostPort(lAddr)
|
||||
|
||||
// Create listener
|
||||
var listener net.Listener
|
||||
var err error
|
||||
for i := 0; i < tryListenSeconds; i++ {
|
||||
listener, err = net.Listen(protocol, lAddr)
|
||||
if err == nil {
|
||||
break
|
||||
} else if i < tryListenSeconds-1 {
|
||||
time.Sleep(time.Second * 1)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
PanicCrisis(err)
|
||||
}
|
||||
// Actual listener local IP & port
|
||||
listenerIP, listenerPort := splitHostPort(listener.Addr().String())
|
||||
log.Info("Local listener", "ip", listenerIP, "port", listenerPort)
|
||||
|
||||
// Determine internal address...
|
||||
var intAddr *NetAddress = NewNetAddressString(lAddr)
|
||||
|
||||
// Determine external address...
|
||||
var extAddr *NetAddress
|
||||
if !config.GetBool("skip_upnp") {
|
||||
// If the lAddrIP is INADDR_ANY, try UPnP
|
||||
if lAddrIP == "" || lAddrIP == "0.0.0.0" {
|
||||
extAddr = getUPNPExternalAddress(lAddrPort, listenerPort)
|
||||
}
|
||||
}
|
||||
// Otherwise just use the local address...
|
||||
if extAddr == nil {
|
||||
extAddr = getNaiveExternalAddress(listenerPort)
|
||||
}
|
||||
if extAddr == nil {
|
||||
PanicCrisis("Could not determine external address!")
|
||||
}
|
||||
|
||||
dl := &DefaultListener{
|
||||
listener: listener,
|
||||
intAddr: intAddr,
|
||||
extAddr: extAddr,
|
||||
connections: make(chan net.Conn, numBufferedConnections),
|
||||
}
|
||||
dl.BaseService = *NewBaseService(log, "DefaultListener", dl)
|
||||
dl.Start() // Started upon construction
|
||||
return dl
|
||||
}
|
||||
|
||||
func (l *DefaultListener) OnStart() error {
|
||||
l.BaseService.OnStart()
|
||||
go l.listenRoutine()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *DefaultListener) OnStop() {
|
||||
l.BaseService.OnStop()
|
||||
l.listener.Close()
|
||||
}
|
||||
|
||||
// Accept connections and pass on the channel
|
||||
func (l *DefaultListener) listenRoutine() {
|
||||
for {
|
||||
conn, err := l.listener.Accept()
|
||||
|
||||
if !l.IsRunning() {
|
||||
break // Go to cleanup
|
||||
}
|
||||
|
||||
// listener wasn't stopped,
|
||||
// yet we encountered an error.
|
||||
if err != nil {
|
||||
PanicCrisis(err)
|
||||
}
|
||||
|
||||
l.connections <- conn
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
close(l.connections)
|
||||
for _ = range l.connections {
|
||||
// Drain
|
||||
}
|
||||
}
|
||||
|
||||
// A channel of inbound connections.
|
||||
// It gets closed when the listener closes.
|
||||
func (l *DefaultListener) Connections() <-chan net.Conn {
|
||||
return l.connections
|
||||
}
|
||||
|
||||
func (l *DefaultListener) InternalAddress() *NetAddress {
|
||||
return l.intAddr
|
||||
}
|
||||
|
||||
func (l *DefaultListener) ExternalAddress() *NetAddress {
|
||||
return l.extAddr
|
||||
}
|
||||
|
||||
// NOTE: The returned listener is already Accept()'ing.
|
||||
// So it's not suitable to pass into http.Serve().
|
||||
func (l *DefaultListener) NetListener() net.Listener {
|
||||
return l.listener
|
||||
}
|
||||
|
||||
func (l *DefaultListener) String() string {
|
||||
return fmt.Sprintf("Listener(@%v)", l.extAddr)
|
||||
}
|
||||
|
||||
/* external address helpers */
|
||||
|
||||
// UPNP external address discovery & port mapping
|
||||
func getUPNPExternalAddress(externalPort, internalPort int) *NetAddress {
|
||||
log.Info("Getting UPNP external address")
|
||||
nat, err := upnp.Discover()
|
||||
if err != nil {
|
||||
log.Info("Could not perform UPNP discover", "error", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
ext, err := nat.GetExternalAddress()
|
||||
if err != nil {
|
||||
log.Info("Could not get UPNP external address", "error", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
// UPnP can't seem to get the external port, so let's just be explicit.
|
||||
if externalPort == 0 {
|
||||
externalPort = defaultExternalPort
|
||||
}
|
||||
|
||||
externalPort, err = nat.AddPortMapping("tcp", externalPort, internalPort, "tendermint", 0)
|
||||
if err != nil {
|
||||
log.Info("Could not add UPNP port mapping", "error", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Info("Got UPNP external address", "address", ext)
|
||||
return NewNetAddressIPPort(ext, uint16(externalPort))
|
||||
}
|
||||
|
||||
// TODO: use syscalls: http://pastebin.com/9exZG4rh
|
||||
func getNaiveExternalAddress(port int) *NetAddress {
|
||||
addrs, err := net.InterfaceAddrs()
|
||||
if err != nil {
|
||||
PanicCrisis(Fmt("Could not fetch interface addresses: %v", err))
|
||||
}
|
||||
|
||||
for _, a := range addrs {
|
||||
ipnet, ok := a.(*net.IPNet)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
v4 := ipnet.IP.To4()
|
||||
if v4 == nil || v4[0] == 127 {
|
||||
continue
|
||||
} // loopback
|
||||
return NewNetAddressIPPort(ipnet.IP, uint16(port))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package p2p
|
||||
|
||||
import (
|
||||
"github.com/tendermint/tendermint/logger"
|
||||
)
|
||||
|
||||
var log = logger.New("module", "p2p")
|
||||
@@ -1,217 +0,0 @@
|
||||
// Modified for Tendermint
|
||||
// Originally Copyright (c) 2013-2014 Conformal Systems LLC.
|
||||
// https://github.com/conformal/btcd/blob/master/LICENSE
|
||||
|
||||
package p2p
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
. "github.com/tendermint/tendermint/common"
|
||||
)
|
||||
|
||||
type NetAddress struct {
|
||||
IP net.IP
|
||||
Port uint16
|
||||
str string
|
||||
}
|
||||
|
||||
// TODO: socks proxies?
|
||||
func NewNetAddress(addr net.Addr) *NetAddress {
|
||||
tcpAddr, ok := addr.(*net.TCPAddr)
|
||||
if !ok {
|
||||
PanicSanity(fmt.Sprintf("Only TCPAddrs are supported. Got: %v", addr))
|
||||
}
|
||||
ip := tcpAddr.IP
|
||||
port := uint16(tcpAddr.Port)
|
||||
return NewNetAddressIPPort(ip, port)
|
||||
}
|
||||
|
||||
// Also resolves the host if host is not an IP.
|
||||
func NewNetAddressString(addr string) *NetAddress {
|
||||
host, portStr, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
PanicSanity(err)
|
||||
}
|
||||
ip := net.ParseIP(host)
|
||||
if ip == nil {
|
||||
if len(host) > 0 {
|
||||
ips, err := net.LookupIP(host)
|
||||
if err != nil {
|
||||
PanicSanity(err)
|
||||
}
|
||||
ip = ips[0]
|
||||
}
|
||||
}
|
||||
port, err := strconv.ParseUint(portStr, 10, 16)
|
||||
if err != nil {
|
||||
PanicSanity(err)
|
||||
}
|
||||
na := NewNetAddressIPPort(ip, uint16(port))
|
||||
return na
|
||||
}
|
||||
|
||||
func NewNetAddressIPPort(ip net.IP, port uint16) *NetAddress {
|
||||
na := &NetAddress{
|
||||
IP: ip,
|
||||
Port: port,
|
||||
str: net.JoinHostPort(
|
||||
ip.String(),
|
||||
strconv.FormatUint(uint64(port), 10),
|
||||
),
|
||||
}
|
||||
return na
|
||||
}
|
||||
|
||||
func (na *NetAddress) Equals(other interface{}) bool {
|
||||
if o, ok := other.(*NetAddress); ok {
|
||||
return na.String() == o.String()
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (na *NetAddress) Less(other interface{}) bool {
|
||||
if o, ok := other.(*NetAddress); ok {
|
||||
return na.String() < o.String()
|
||||
} else {
|
||||
PanicSanity("Cannot compare unequal types")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (na *NetAddress) String() string {
|
||||
if na.str == "" {
|
||||
na.str = net.JoinHostPort(
|
||||
na.IP.String(),
|
||||
strconv.FormatUint(uint64(na.Port), 10),
|
||||
)
|
||||
}
|
||||
return na.str
|
||||
}
|
||||
|
||||
func (na *NetAddress) Dial() (net.Conn, error) {
|
||||
conn, err := net.Dial("tcp", na.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (na *NetAddress) DialTimeout(timeout time.Duration) (net.Conn, error) {
|
||||
conn, err := net.DialTimeout("tcp", na.String(), timeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (na *NetAddress) Routable() bool {
|
||||
if config.GetBool("local_routing") {
|
||||
return na.Valid()
|
||||
}
|
||||
|
||||
// TODO(oga) bitcoind doesn't include RFC3849 here, but should we?
|
||||
return na.Valid() && !(na.RFC1918() || na.RFC3927() || na.RFC4862() ||
|
||||
na.RFC4193() || na.RFC4843() || na.Local())
|
||||
}
|
||||
|
||||
// For IPv4 these are either a 0 or all bits set address. For IPv6 a zero
|
||||
// address or one that matches the RFC3849 documentation address format.
|
||||
func (na *NetAddress) Valid() bool {
|
||||
return na.IP != nil && !(na.IP.IsUnspecified() || na.RFC3849() ||
|
||||
na.IP.Equal(net.IPv4bcast))
|
||||
}
|
||||
|
||||
func (na *NetAddress) Local() bool {
|
||||
return na.IP.IsLoopback() || zero4.Contains(na.IP)
|
||||
}
|
||||
|
||||
func (na *NetAddress) ReachabilityTo(o *NetAddress) int {
|
||||
const (
|
||||
Unreachable = 0
|
||||
Default = iota
|
||||
Teredo
|
||||
Ipv6_weak
|
||||
Ipv4
|
||||
Ipv6_strong
|
||||
Private
|
||||
)
|
||||
if !na.Routable() {
|
||||
return Unreachable
|
||||
} else if na.RFC4380() {
|
||||
if !o.Routable() {
|
||||
return Default
|
||||
} else if o.RFC4380() {
|
||||
return Teredo
|
||||
} else if o.IP.To4() != nil {
|
||||
return Ipv4
|
||||
} else { // ipv6
|
||||
return Ipv6_weak
|
||||
}
|
||||
} else if na.IP.To4() != nil {
|
||||
if o.Routable() && o.IP.To4() != nil {
|
||||
return Ipv4
|
||||
}
|
||||
return Default
|
||||
} else /* ipv6 */ {
|
||||
var tunnelled bool
|
||||
// Is our v6 is tunnelled?
|
||||
if o.RFC3964() || o.RFC6052() || o.RFC6145() {
|
||||
tunnelled = true
|
||||
}
|
||||
if !o.Routable() {
|
||||
return Default
|
||||
} else if o.RFC4380() {
|
||||
return Teredo
|
||||
} else if o.IP.To4() != nil {
|
||||
return Ipv4
|
||||
} else if tunnelled {
|
||||
// only prioritise ipv6 if we aren't tunnelling it.
|
||||
return Ipv6_weak
|
||||
}
|
||||
return Ipv6_strong
|
||||
}
|
||||
}
|
||||
|
||||
// RFC1918: IPv4 Private networks (10.0.0.0/8, 192.168.0.0/16, 172.16.0.0/12)
|
||||
// RFC3849: IPv6 Documentation address (2001:0DB8::/32)
|
||||
// RFC3927: IPv4 Autoconfig (169.254.0.0/16)
|
||||
// RFC3964: IPv6 6to4 (2002::/16)
|
||||
// RFC4193: IPv6 unique local (FC00::/7)
|
||||
// RFC4380: IPv6 Teredo tunneling (2001::/32)
|
||||
// RFC4843: IPv6 ORCHID: (2001:10::/28)
|
||||
// RFC4862: IPv6 Autoconfig (FE80::/64)
|
||||
// RFC6052: IPv6 well known prefix (64:FF9B::/96)
|
||||
// RFC6145: IPv6 IPv4 translated address ::FFFF:0:0:0/96
|
||||
var rfc1918_10 = net.IPNet{IP: net.ParseIP("10.0.0.0"), Mask: net.CIDRMask(8, 32)}
|
||||
var rfc1918_192 = net.IPNet{IP: net.ParseIP("192.168.0.0"), Mask: net.CIDRMask(16, 32)}
|
||||
var rfc1918_172 = net.IPNet{IP: net.ParseIP("172.16.0.0"), Mask: net.CIDRMask(12, 32)}
|
||||
var rfc3849 = net.IPNet{IP: net.ParseIP("2001:0DB8::"), Mask: net.CIDRMask(32, 128)}
|
||||
var rfc3927 = net.IPNet{IP: net.ParseIP("169.254.0.0"), Mask: net.CIDRMask(16, 32)}
|
||||
var rfc3964 = net.IPNet{IP: net.ParseIP("2002::"), Mask: net.CIDRMask(16, 128)}
|
||||
var rfc4193 = net.IPNet{IP: net.ParseIP("FC00::"), Mask: net.CIDRMask(7, 128)}
|
||||
var rfc4380 = net.IPNet{IP: net.ParseIP("2001::"), Mask: net.CIDRMask(32, 128)}
|
||||
var rfc4843 = net.IPNet{IP: net.ParseIP("2001:10::"), Mask: net.CIDRMask(28, 128)}
|
||||
var rfc4862 = net.IPNet{IP: net.ParseIP("FE80::"), Mask: net.CIDRMask(64, 128)}
|
||||
var rfc6052 = net.IPNet{IP: net.ParseIP("64:FF9B::"), Mask: net.CIDRMask(96, 128)}
|
||||
var rfc6145 = net.IPNet{IP: net.ParseIP("::FFFF:0:0:0"), Mask: net.CIDRMask(96, 128)}
|
||||
var zero4 = net.IPNet{IP: net.ParseIP("0.0.0.0"), Mask: net.CIDRMask(8, 32)}
|
||||
|
||||
func (na *NetAddress) RFC1918() bool {
|
||||
return rfc1918_10.Contains(na.IP) ||
|
||||
rfc1918_192.Contains(na.IP) ||
|
||||
rfc1918_172.Contains(na.IP)
|
||||
}
|
||||
func (na *NetAddress) RFC3849() bool { return rfc3849.Contains(na.IP) }
|
||||
func (na *NetAddress) RFC3927() bool { return rfc3927.Contains(na.IP) }
|
||||
func (na *NetAddress) RFC3964() bool { return rfc3964.Contains(na.IP) }
|
||||
func (na *NetAddress) RFC4193() bool { return rfc4193.Contains(na.IP) }
|
||||
func (na *NetAddress) RFC4380() bool { return rfc4380.Contains(na.IP) }
|
||||
func (na *NetAddress) RFC4843() bool { return rfc4843.Contains(na.IP) }
|
||||
func (na *NetAddress) RFC4862() bool { return rfc4862.Contains(na.IP) }
|
||||
func (na *NetAddress) RFC6052() bool { return rfc6052.Contains(na.IP) }
|
||||
func (na *NetAddress) RFC6145() bool { return rfc6145.Contains(na.IP) }
|
||||
-134
@@ -1,134 +0,0 @@
|
||||
package p2p
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
|
||||
. "github.com/tendermint/tendermint/common"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
"github.com/tendermint/tendermint/wire"
|
||||
)
|
||||
|
||||
type Peer struct {
|
||||
BaseService
|
||||
|
||||
outbound bool
|
||||
mconn *MConnection
|
||||
|
||||
*types.NodeInfo
|
||||
Key string
|
||||
Data *CMap // User data.
|
||||
}
|
||||
|
||||
// NOTE: blocking
|
||||
// Before creating a peer with newPeer(), perform a handshake on connection.
|
||||
func peerHandshake(conn net.Conn, ourNodeInfo *types.NodeInfo) (*types.NodeInfo, error) {
|
||||
var peerNodeInfo = new(types.NodeInfo)
|
||||
var err1 error
|
||||
var err2 error
|
||||
Parallel(
|
||||
func() {
|
||||
var n int64
|
||||
wire.WriteBinary(ourNodeInfo, conn, &n, &err1)
|
||||
},
|
||||
func() {
|
||||
var n int64
|
||||
wire.ReadBinary(peerNodeInfo, conn, &n, &err2)
|
||||
log.Notice("Peer handshake", "peerNodeInfo", peerNodeInfo)
|
||||
})
|
||||
if err1 != nil {
|
||||
return nil, err1
|
||||
}
|
||||
if err2 != nil {
|
||||
return nil, err2
|
||||
}
|
||||
return peerNodeInfo, nil
|
||||
}
|
||||
|
||||
// NOTE: call peerHandshake on conn before calling newPeer().
|
||||
func newPeer(conn net.Conn, peerNodeInfo *types.NodeInfo, outbound bool, reactorsByCh map[byte]Reactor, chDescs []*ChannelDescriptor, onPeerError func(*Peer, interface{})) *Peer {
|
||||
var p *Peer
|
||||
onReceive := func(chID byte, msgBytes []byte) {
|
||||
reactor := reactorsByCh[chID]
|
||||
if reactor == nil {
|
||||
PanicSanity(Fmt("Unknown channel %X", chID))
|
||||
}
|
||||
reactor.Receive(chID, p, msgBytes)
|
||||
}
|
||||
onError := func(r interface{}) {
|
||||
p.Stop()
|
||||
onPeerError(p, r)
|
||||
}
|
||||
mconn := NewMConnection(conn, chDescs, onReceive, onError)
|
||||
p = &Peer{
|
||||
outbound: outbound,
|
||||
mconn: mconn,
|
||||
NodeInfo: peerNodeInfo,
|
||||
Key: peerNodeInfo.PubKey.KeyString(),
|
||||
Data: NewCMap(),
|
||||
}
|
||||
p.BaseService = *NewBaseService(log, "Peer", p)
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *Peer) OnStart() error {
|
||||
p.BaseService.OnStart()
|
||||
_, err := p.mconn.Start()
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *Peer) OnStop() {
|
||||
p.BaseService.OnStop()
|
||||
p.mconn.Stop()
|
||||
}
|
||||
|
||||
func (p *Peer) Connection() *MConnection {
|
||||
return p.mconn
|
||||
}
|
||||
|
||||
func (p *Peer) IsOutbound() bool {
|
||||
return p.outbound
|
||||
}
|
||||
|
||||
func (p *Peer) Send(chID byte, msg interface{}) bool {
|
||||
if !p.IsRunning() {
|
||||
return false
|
||||
}
|
||||
return p.mconn.Send(chID, msg)
|
||||
}
|
||||
|
||||
func (p *Peer) TrySend(chID byte, msg interface{}) bool {
|
||||
if !p.IsRunning() {
|
||||
return false
|
||||
}
|
||||
return p.mconn.TrySend(chID, msg)
|
||||
}
|
||||
|
||||
func (p *Peer) CanSend(chID byte) bool {
|
||||
if !p.IsRunning() {
|
||||
return false
|
||||
}
|
||||
return p.mconn.CanSend(chID)
|
||||
}
|
||||
|
||||
func (p *Peer) WriteTo(w io.Writer) (n int64, err error) {
|
||||
wire.WriteString(p.Key, w, &n, &err)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *Peer) String() string {
|
||||
if p.outbound {
|
||||
return fmt.Sprintf("Peer{%v %v out}", p.mconn, p.Key[:12])
|
||||
} else {
|
||||
return fmt.Sprintf("Peer{%v %v in}", p.mconn, p.Key[:12])
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Peer) Equals(other *Peer) bool {
|
||||
return p.Key == other.Key
|
||||
}
|
||||
|
||||
func (p *Peer) Get(key string) interface{} {
|
||||
return p.Data.Get(key)
|
||||
}
|
||||
-227
@@ -1,227 +0,0 @@
|
||||
package p2p
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// IPeerSet has a (immutable) subset of the methods of PeerSet.
|
||||
type IPeerSet interface {
|
||||
Has(key string) bool
|
||||
Get(key string) *Peer
|
||||
List() []*Peer
|
||||
Size() int
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
var (
|
||||
maxPeersPerIPRange = [4]int{11, 7, 5, 3} // ...
|
||||
)
|
||||
|
||||
// PeerSet is a special structure for keeping a table of peers.
|
||||
// Iteration over the peers is super fast and thread-safe.
|
||||
// We also track how many peers per IP range and avoid too many
|
||||
type PeerSet struct {
|
||||
mtx sync.Mutex
|
||||
lookup map[string]*peerSetItem
|
||||
list []*Peer
|
||||
connectedIPs *nestedCounter
|
||||
}
|
||||
|
||||
type peerSetItem struct {
|
||||
peer *Peer
|
||||
index int
|
||||
}
|
||||
|
||||
func NewPeerSet() *PeerSet {
|
||||
return &PeerSet{
|
||||
lookup: make(map[string]*peerSetItem),
|
||||
list: make([]*Peer, 0, 256),
|
||||
connectedIPs: NewNestedCounter(),
|
||||
}
|
||||
}
|
||||
|
||||
// Returns false if peer with key (PubKeyEd25519) is already in set
|
||||
// or if we have too many peers from the peer's IP range
|
||||
func (ps *PeerSet) Add(peer *Peer) error {
|
||||
ps.mtx.Lock()
|
||||
defer ps.mtx.Unlock()
|
||||
if ps.lookup[peer.Key] != nil {
|
||||
return ErrSwitchDuplicatePeer
|
||||
}
|
||||
|
||||
// ensure we havent maxed out connections for the peer's IP range yet
|
||||
// and update the IP range counters
|
||||
if !ps.incrIPRangeCounts(peer.Host) {
|
||||
return ErrSwitchMaxPeersPerIPRange
|
||||
}
|
||||
|
||||
index := len(ps.list)
|
||||
// Appending is safe even with other goroutines
|
||||
// iterating over the ps.list slice.
|
||||
ps.list = append(ps.list, peer)
|
||||
ps.lookup[peer.Key] = &peerSetItem{peer, index}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ps *PeerSet) Has(peerKey string) bool {
|
||||
ps.mtx.Lock()
|
||||
defer ps.mtx.Unlock()
|
||||
_, ok := ps.lookup[peerKey]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (ps *PeerSet) Get(peerKey string) *Peer {
|
||||
ps.mtx.Lock()
|
||||
defer ps.mtx.Unlock()
|
||||
item, ok := ps.lookup[peerKey]
|
||||
if ok {
|
||||
return item.peer
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (ps *PeerSet) Remove(peer *Peer) {
|
||||
ps.mtx.Lock()
|
||||
defer ps.mtx.Unlock()
|
||||
item := ps.lookup[peer.Key]
|
||||
if item == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// update the IP range counters
|
||||
ps.decrIPRangeCounts(peer.Host)
|
||||
|
||||
index := item.index
|
||||
// Copy the list but without the last element.
|
||||
// (we must copy because we're mutating the list)
|
||||
newList := make([]*Peer, len(ps.list)-1)
|
||||
copy(newList, ps.list)
|
||||
// If it's the last peer, that's an easy special case.
|
||||
if index == len(ps.list)-1 {
|
||||
ps.list = newList
|
||||
delete(ps.lookup, peer.Key)
|
||||
return
|
||||
}
|
||||
|
||||
// Move the last item from ps.list to "index" in list.
|
||||
lastPeer := ps.list[len(ps.list)-1]
|
||||
lastPeerKey := lastPeer.Key
|
||||
lastPeerItem := ps.lookup[lastPeerKey]
|
||||
newList[index] = lastPeer
|
||||
lastPeerItem.index = index
|
||||
ps.list = newList
|
||||
delete(ps.lookup, peer.Key)
|
||||
|
||||
}
|
||||
|
||||
func (ps *PeerSet) Size() int {
|
||||
ps.mtx.Lock()
|
||||
defer ps.mtx.Unlock()
|
||||
return len(ps.list)
|
||||
}
|
||||
|
||||
// threadsafe list of peers.
|
||||
func (ps *PeerSet) List() []*Peer {
|
||||
ps.mtx.Lock()
|
||||
defer ps.mtx.Unlock()
|
||||
return ps.list
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// track the number of IPs we're connected to for each IP address range
|
||||
|
||||
// forms an IP address hierarchy tree with counts
|
||||
// the struct itself is not thread safe and should always only be accessed with the ps.mtx locked
|
||||
type nestedCounter struct {
|
||||
count int
|
||||
children map[string]*nestedCounter
|
||||
}
|
||||
|
||||
func NewNestedCounter() *nestedCounter {
|
||||
nc := new(nestedCounter)
|
||||
nc.children = make(map[string]*nestedCounter)
|
||||
return nc
|
||||
}
|
||||
|
||||
// Check if we have too many IPs in the IP range of the incoming connection
|
||||
// Thread safe
|
||||
func (ps *PeerSet) HasMaxForIPRange(conn net.Conn) (ok bool) {
|
||||
ps.mtx.Lock()
|
||||
defer ps.mtx.Unlock()
|
||||
ip, _, _ := net.SplitHostPort(conn.RemoteAddr().String())
|
||||
ipBytes := strings.Split(ip, ".")
|
||||
|
||||
c := ps.connectedIPs
|
||||
for i, ipByte := range ipBytes {
|
||||
if c, ok = c.children[ipByte]; !ok {
|
||||
return false
|
||||
}
|
||||
if maxPeersPerIPRange[i] <= c.count {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Increments counts for this address' IP range
|
||||
// Returns false if we already have enough connections
|
||||
// Not thread safe (only called by ps.Add())
|
||||
func (ps *PeerSet) incrIPRangeCounts(address string) bool {
|
||||
addrParts := strings.Split(address, ".")
|
||||
|
||||
c := ps.connectedIPs
|
||||
return incrNestedCounters(c, addrParts, 0)
|
||||
}
|
||||
|
||||
// Recursively descend the IP hierarchy, checking if we have
|
||||
// max peers for each range and incrementing if not.
|
||||
// Returns false if incr failed because max peers reached for some range counter.
|
||||
func incrNestedCounters(c *nestedCounter, ipBytes []string, index int) bool {
|
||||
ipByte := ipBytes[index]
|
||||
child := c.children[ipByte]
|
||||
if child == nil {
|
||||
child = NewNestedCounter()
|
||||
c.children[ipByte] = child
|
||||
}
|
||||
if index+1 < len(ipBytes) {
|
||||
if !incrNestedCounters(child, ipBytes, index+1) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if maxPeersPerIPRange[index] <= child.count {
|
||||
return false
|
||||
} else {
|
||||
child.count += 1
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Decrement counts for this address' IP range
|
||||
func (ps *PeerSet) decrIPRangeCounts(address string) {
|
||||
addrParts := strings.Split(address, ".")
|
||||
|
||||
c := ps.connectedIPs
|
||||
decrNestedCounters(c, addrParts, 0)
|
||||
}
|
||||
|
||||
// Recursively descend the IP hierarchy, decrementing by one.
|
||||
// If the counter is zero, deletes the child.
|
||||
func decrNestedCounters(c *nestedCounter, ipBytes []string, index int) {
|
||||
ipByte := ipBytes[index]
|
||||
child := c.children[ipByte]
|
||||
if child == nil {
|
||||
log.Error("p2p/peer_set decrNestedCounters encountered a missing child counter")
|
||||
return
|
||||
}
|
||||
if index+1 < len(ipBytes) {
|
||||
decrNestedCounters(child, ipBytes, index+1)
|
||||
}
|
||||
child.count -= 1
|
||||
if child.count <= 0 {
|
||||
delete(c.children, ipByte)
|
||||
}
|
||||
}
|
||||
@@ -1,169 +0,0 @@
|
||||
package p2p
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
. "github.com/tendermint/tendermint/common"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
// Returns an empty dummy peer
|
||||
func randPeer() *Peer {
|
||||
return &Peer{
|
||||
Key: RandStr(12),
|
||||
NodeInfo: &types.NodeInfo{
|
||||
Host: Fmt("%v.%v.%v.%v", rand.Int()%256, rand.Int()%256, rand.Int()%256, rand.Int()%256),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddRemoveOne(t *testing.T) {
|
||||
peerSet := NewPeerSet()
|
||||
|
||||
peer := randPeer()
|
||||
err := peerSet.Add(peer)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to add new peer")
|
||||
}
|
||||
if peerSet.Size() != 1 {
|
||||
t.Errorf("Failed to add new peer and increment size")
|
||||
}
|
||||
|
||||
peerSet.Remove(peer)
|
||||
if peerSet.Has(peer.Key) {
|
||||
t.Errorf("Failed to remove peer")
|
||||
}
|
||||
if peerSet.Size() != 0 {
|
||||
t.Errorf("Failed to remove peer and decrement size")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddRemoveMany(t *testing.T) {
|
||||
peerSet := NewPeerSet()
|
||||
|
||||
peers := []*Peer{}
|
||||
N := 100
|
||||
maxPeersPerIPRange = [4]int{N, N, N, N}
|
||||
for i := 0; i < N; i++ {
|
||||
peer := randPeer()
|
||||
if err := peerSet.Add(peer); err != nil {
|
||||
t.Errorf("Failed to add new peer")
|
||||
}
|
||||
if peerSet.Size() != i+1 {
|
||||
t.Errorf("Failed to add new peer and increment size")
|
||||
}
|
||||
peers = append(peers, peer)
|
||||
}
|
||||
|
||||
for i, peer := range peers {
|
||||
peerSet.Remove(peer)
|
||||
if peerSet.Has(peer.Key) {
|
||||
t.Errorf("Failed to remove peer")
|
||||
}
|
||||
if peerSet.Size() != len(peers)-i-1 {
|
||||
t.Errorf("Failed to remove peer and decrement size")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func newPeerInIPRange(ipBytes ...string) *Peer {
|
||||
ips := make([]string, 4)
|
||||
for i, ipByte := range ipBytes {
|
||||
ips[i] = ipByte
|
||||
}
|
||||
for i := len(ipBytes); i < 4; i++ {
|
||||
ips[i] = Fmt("%v", rand.Int()%256)
|
||||
}
|
||||
ipS := strings.Join(ips, ".")
|
||||
return &Peer{
|
||||
Key: RandStr(12),
|
||||
NodeInfo: &types.NodeInfo{
|
||||
Host: ipS,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestIPRanges(t *testing.T) {
|
||||
peerSet := NewPeerSet()
|
||||
|
||||
// test /8
|
||||
maxPeersPerIPRange = [4]int{2, 2, 2, 2}
|
||||
peer := newPeerInIPRange("54", "1")
|
||||
if err := peerSet.Add(peer); err != nil {
|
||||
t.Errorf("Failed to add new peer")
|
||||
}
|
||||
peer = newPeerInIPRange("54", "2")
|
||||
if err := peerSet.Add(peer); err != nil {
|
||||
t.Errorf("Failed to add new peer")
|
||||
}
|
||||
peer = newPeerInIPRange("54", "3")
|
||||
if err := peerSet.Add(peer); err == nil {
|
||||
t.Errorf("Added peer when we shouldn't have")
|
||||
}
|
||||
peer = newPeerInIPRange("55", "1")
|
||||
if err := peerSet.Add(peer); err != nil {
|
||||
t.Errorf("Failed to add new peer")
|
||||
}
|
||||
|
||||
// test /16
|
||||
peerSet = NewPeerSet()
|
||||
maxPeersPerIPRange = [4]int{3, 2, 1, 1}
|
||||
peer = newPeerInIPRange("54", "112", "1")
|
||||
if err := peerSet.Add(peer); err != nil {
|
||||
t.Errorf("Failed to add new peer")
|
||||
}
|
||||
peer = newPeerInIPRange("54", "112", "2")
|
||||
if err := peerSet.Add(peer); err != nil {
|
||||
t.Errorf("Failed to add new peer")
|
||||
}
|
||||
peer = newPeerInIPRange("54", "112", "3")
|
||||
if err := peerSet.Add(peer); err == nil {
|
||||
t.Errorf("Added peer when we shouldn't have")
|
||||
}
|
||||
peer = newPeerInIPRange("54", "113", "1")
|
||||
if err := peerSet.Add(peer); err != nil {
|
||||
t.Errorf("Failed to add new peer")
|
||||
}
|
||||
|
||||
// test /24
|
||||
peerSet = NewPeerSet()
|
||||
maxPeersPerIPRange = [4]int{5, 3, 2, 1}
|
||||
peer = newPeerInIPRange("54", "112", "11", "1")
|
||||
if err := peerSet.Add(peer); err != nil {
|
||||
t.Errorf("Failed to add new peer")
|
||||
}
|
||||
peer = newPeerInIPRange("54", "112", "11", "2")
|
||||
if err := peerSet.Add(peer); err != nil {
|
||||
t.Errorf("Failed to add new peer")
|
||||
}
|
||||
peer = newPeerInIPRange("54", "112", "11", "3")
|
||||
if err := peerSet.Add(peer); err == nil {
|
||||
t.Errorf("Added peer when we shouldn't have")
|
||||
}
|
||||
peer = newPeerInIPRange("54", "112", "12", "1")
|
||||
if err := peerSet.Add(peer); err != nil {
|
||||
t.Errorf("Failed to add new peer")
|
||||
}
|
||||
|
||||
// test /32
|
||||
peerSet = NewPeerSet()
|
||||
maxPeersPerIPRange = [4]int{11, 7, 5, 2}
|
||||
peer = newPeerInIPRange("54", "112", "11", "10")
|
||||
if err := peerSet.Add(peer); err != nil {
|
||||
t.Errorf("Failed to add new peer")
|
||||
}
|
||||
peer = newPeerInIPRange("54", "112", "11", "10")
|
||||
if err := peerSet.Add(peer); err != nil {
|
||||
t.Errorf("Failed to add new peer")
|
||||
}
|
||||
peer = newPeerInIPRange("54", "112", "11", "10")
|
||||
if err := peerSet.Add(peer); err == nil {
|
||||
t.Errorf("Added peer when we shouldn't have")
|
||||
}
|
||||
peer = newPeerInIPRange("54", "112", "11", "11")
|
||||
if err := peerSet.Add(peer); err != nil {
|
||||
t.Errorf("Failed to add new peer")
|
||||
}
|
||||
}
|
||||
@@ -1,262 +0,0 @@
|
||||
package p2p
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
. "github.com/tendermint/tendermint/common"
|
||||
"github.com/tendermint/tendermint/events"
|
||||
"github.com/tendermint/tendermint/wire"
|
||||
)
|
||||
|
||||
var pexErrInvalidMessage = errors.New("Invalid PEX message")
|
||||
|
||||
const (
|
||||
PexChannel = byte(0x00)
|
||||
ensurePeersPeriodSeconds = 30
|
||||
minNumOutboundPeers = 10
|
||||
)
|
||||
|
||||
/*
|
||||
PEXReactor handles PEX (peer exchange) and ensures that an
|
||||
adequate number of peers are connected to the switch.
|
||||
*/
|
||||
type PEXReactor struct {
|
||||
BaseReactor
|
||||
|
||||
sw *Switch
|
||||
book *AddrBook
|
||||
evsw events.Fireable
|
||||
}
|
||||
|
||||
func NewPEXReactor(book *AddrBook) *PEXReactor {
|
||||
pexR := &PEXReactor{
|
||||
book: book,
|
||||
}
|
||||
pexR.BaseReactor = *NewBaseReactor(log, "PEXReactor", pexR)
|
||||
return pexR
|
||||
}
|
||||
|
||||
func (pexR *PEXReactor) OnStart() error {
|
||||
pexR.BaseReactor.OnStart()
|
||||
go pexR.ensurePeersRoutine()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pexR *PEXReactor) OnStop() {
|
||||
pexR.BaseReactor.OnStop()
|
||||
}
|
||||
|
||||
// Implements Reactor
|
||||
func (pexR *PEXReactor) GetChannels() []*ChannelDescriptor {
|
||||
return []*ChannelDescriptor{
|
||||
&ChannelDescriptor{
|
||||
ID: PexChannel,
|
||||
Priority: 1,
|
||||
SendQueueCapacity: 10,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Implements Reactor
|
||||
func (pexR *PEXReactor) AddPeer(peer *Peer) {
|
||||
// Add the peer to the address book
|
||||
netAddr := NewNetAddressString(fmt.Sprintf("%s:%d", peer.Host, peer.P2PPort))
|
||||
if peer.IsOutbound() {
|
||||
if pexR.book.NeedMoreAddrs() {
|
||||
pexR.RequestPEX(peer)
|
||||
}
|
||||
} else {
|
||||
// For inbound connections, the peer is its own source
|
||||
// (For outbound peers, the address is already in the books)
|
||||
pexR.book.AddAddress(netAddr, netAddr)
|
||||
}
|
||||
}
|
||||
|
||||
// Implements Reactor
|
||||
func (pexR *PEXReactor) RemovePeer(peer *Peer, reason interface{}) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
// Implements Reactor
|
||||
// Handles incoming PEX messages.
|
||||
func (pexR *PEXReactor) Receive(chID byte, src *Peer, msgBytes []byte) {
|
||||
|
||||
// decode message
|
||||
_, msg, err := DecodeMessage(msgBytes)
|
||||
if err != nil {
|
||||
log.Warn("Error decoding message", "error", err)
|
||||
return
|
||||
}
|
||||
log.Notice("Received message", "msg", msg)
|
||||
|
||||
switch msg := msg.(type) {
|
||||
case *pexRequestMessage:
|
||||
// src requested some peers.
|
||||
// TODO: prevent abuse.
|
||||
pexR.SendAddrs(src, pexR.book.GetSelection())
|
||||
case *pexAddrsMessage:
|
||||
// We received some peer addresses from src.
|
||||
// TODO: prevent abuse.
|
||||
// (We don't want to get spammed with bad peers)
|
||||
srcAddr := src.Connection().RemoteAddress
|
||||
for _, addr := range msg.Addrs {
|
||||
pexR.book.AddAddress(addr, srcAddr)
|
||||
}
|
||||
default:
|
||||
log.Warn(Fmt("Unknown message type %v", reflect.TypeOf(msg)))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Asks peer for more addresses.
|
||||
func (pexR *PEXReactor) RequestPEX(peer *Peer) {
|
||||
peer.Send(PexChannel, &pexRequestMessage{})
|
||||
}
|
||||
|
||||
func (pexR *PEXReactor) SendAddrs(peer *Peer, addrs []*NetAddress) {
|
||||
peer.Send(PexChannel, &pexAddrsMessage{Addrs: addrs})
|
||||
}
|
||||
|
||||
// Ensures that sufficient peers are connected. (continuous)
|
||||
func (pexR *PEXReactor) ensurePeersRoutine() {
|
||||
// Randomize when routine starts
|
||||
time.Sleep(time.Duration(rand.Int63n(500*ensurePeersPeriodSeconds)) * time.Millisecond)
|
||||
|
||||
// fire once immediately.
|
||||
pexR.ensurePeers()
|
||||
// fire periodically
|
||||
timer := NewRepeatTimer("pex", ensurePeersPeriodSeconds*time.Second)
|
||||
FOR_LOOP:
|
||||
for {
|
||||
select {
|
||||
case <-timer.Ch:
|
||||
pexR.ensurePeers()
|
||||
case <-pexR.Quit:
|
||||
break FOR_LOOP
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
timer.Stop()
|
||||
}
|
||||
|
||||
// Ensures that sufficient peers are connected. (once)
|
||||
func (pexR *PEXReactor) ensurePeers() {
|
||||
numOutPeers, _, numDialing := pexR.Switch.NumPeers()
|
||||
numToDial := minNumOutboundPeers - (numOutPeers + numDialing)
|
||||
log.Info("Ensure peers", "numOutPeers", numOutPeers, "numDialing", numDialing, "numToDial", numToDial)
|
||||
if numToDial <= 0 {
|
||||
return
|
||||
}
|
||||
toDial := NewCMap()
|
||||
|
||||
// Try to pick numToDial addresses to dial.
|
||||
// TODO: improve logic.
|
||||
for i := 0; i < numToDial; i++ {
|
||||
newBias := MinInt(numOutPeers, 8)*10 + 10
|
||||
var picked *NetAddress
|
||||
// Try to fetch a new peer 3 times.
|
||||
// This caps the maximum number of tries to 3 * numToDial.
|
||||
for j := 0; j < 3; j++ {
|
||||
try := pexR.book.PickAddress(newBias)
|
||||
if try == nil {
|
||||
break
|
||||
}
|
||||
alreadySelected := toDial.Has(try.IP.String())
|
||||
alreadyDialing := pexR.Switch.IsDialing(try)
|
||||
alreadyConnected := pexR.Switch.Peers().Has(try.IP.String())
|
||||
if alreadySelected || alreadyDialing || alreadyConnected {
|
||||
/*
|
||||
log.Info("Cannot dial address", "addr", try,
|
||||
"alreadySelected", alreadySelected,
|
||||
"alreadyDialing", alreadyDialing,
|
||||
"alreadyConnected", alreadyConnected)
|
||||
*/
|
||||
continue
|
||||
} else {
|
||||
log.Info("Will dial address", "addr", try)
|
||||
picked = try
|
||||
break
|
||||
}
|
||||
}
|
||||
if picked == nil {
|
||||
continue
|
||||
}
|
||||
toDial.Set(picked.IP.String(), picked)
|
||||
}
|
||||
|
||||
// Dial picked addresses
|
||||
for _, item := range toDial.Values() {
|
||||
go func(picked *NetAddress) {
|
||||
_, err := pexR.Switch.DialPeerWithAddress(picked)
|
||||
if err != nil {
|
||||
pexR.book.MarkAttempt(picked)
|
||||
}
|
||||
}(item.(*NetAddress))
|
||||
}
|
||||
|
||||
// If we need more addresses, pick a random peer and ask for more.
|
||||
if pexR.book.NeedMoreAddrs() {
|
||||
if peers := pexR.Switch.Peers().List(); len(peers) > 0 {
|
||||
i := rand.Int() % len(peers)
|
||||
peer := peers[i]
|
||||
log.Info("No addresses to dial. Sending pexRequest to random peer", "peer", peer)
|
||||
pexR.RequestPEX(peer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// implements events.Eventable
|
||||
func (pexR *PEXReactor) SetFireable(evsw events.Fireable) {
|
||||
pexR.evsw = evsw
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Messages
|
||||
|
||||
const (
|
||||
msgTypeRequest = byte(0x01)
|
||||
msgTypeAddrs = byte(0x02)
|
||||
)
|
||||
|
||||
type PexMessage interface{}
|
||||
|
||||
var _ = wire.RegisterInterface(
|
||||
struct{ PexMessage }{},
|
||||
wire.ConcreteType{&pexRequestMessage{}, msgTypeRequest},
|
||||
wire.ConcreteType{&pexAddrsMessage{}, msgTypeAddrs},
|
||||
)
|
||||
|
||||
func DecodeMessage(bz []byte) (msgType byte, msg PexMessage, err error) {
|
||||
msgType = bz[0]
|
||||
n := new(int64)
|
||||
r := bytes.NewReader(bz)
|
||||
msg = wire.ReadBinary(struct{ PexMessage }{}, r, n, &err).(struct{ PexMessage }).PexMessage
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
A pexRequestMessage requests additional peer addresses.
|
||||
*/
|
||||
type pexRequestMessage struct {
|
||||
}
|
||||
|
||||
func (m *pexRequestMessage) String() string {
|
||||
return "[pexRequest]"
|
||||
}
|
||||
|
||||
/*
|
||||
A message with announced peer addresses.
|
||||
*/
|
||||
type pexAddrsMessage struct {
|
||||
Addrs []*NetAddress
|
||||
}
|
||||
|
||||
func (m *pexAddrsMessage) String() string {
|
||||
return fmt.Sprintf("[pexAddrs %v]", m.Addrs)
|
||||
}
|
||||
@@ -1,346 +0,0 @@
|
||||
// Uses nacl's secret_box to encrypt a net.Conn.
|
||||
// It is (meant to be) an implementation of the STS protocol.
|
||||
// Note we do not (yet) assume that a remote peer's pubkey
|
||||
// is known ahead of time, and thus we are technically
|
||||
// still vulnerable to MITM. (TODO!)
|
||||
// See docs/sts-final.pdf for more info
|
||||
package p2p
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
crand "crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/tendermint/tendermint/Godeps/_workspace/src/golang.org/x/crypto/nacl/box"
|
||||
"github.com/tendermint/tendermint/Godeps/_workspace/src/golang.org/x/crypto/nacl/secretbox"
|
||||
"github.com/tendermint/tendermint/Godeps/_workspace/src/golang.org/x/crypto/ripemd160"
|
||||
|
||||
acm "github.com/tendermint/tendermint/account"
|
||||
. "github.com/tendermint/tendermint/common"
|
||||
"github.com/tendermint/tendermint/wire"
|
||||
)
|
||||
|
||||
// 2 + 1024 == 1026 total frame size
|
||||
const dataLenSize = 2 // uint16 to describe the length, is <= dataMaxSize
|
||||
const dataMaxSize = 1024
|
||||
const totalFrameSize = dataMaxSize + dataLenSize
|
||||
const sealedFrameSize = totalFrameSize + secretbox.Overhead
|
||||
const authSigMsgSize = (32 + 1) + (64 + 1) // fixed size (length prefixed) byte arrays
|
||||
|
||||
// Implements net.Conn
|
||||
type SecretConnection struct {
|
||||
conn io.ReadWriteCloser
|
||||
recvBuffer []byte
|
||||
recvNonce *[24]byte
|
||||
sendNonce *[24]byte
|
||||
remPubKey acm.PubKeyEd25519
|
||||
shrSecret *[32]byte // shared secret
|
||||
}
|
||||
|
||||
// Performs handshake and returns a new authenticated SecretConnection.
|
||||
// Returns nil if error in handshake.
|
||||
// Caller should call conn.Close()
|
||||
// See docs/sts-final.pdf for more information.
|
||||
func MakeSecretConnection(conn io.ReadWriteCloser, locPrivKey acm.PrivKeyEd25519) (*SecretConnection, error) {
|
||||
|
||||
locPubKey := locPrivKey.PubKey().(acm.PubKeyEd25519)
|
||||
|
||||
// Generate ephemeral keys for perfect forward secrecy.
|
||||
locEphPub, locEphPriv := genEphKeys()
|
||||
|
||||
// Write local ephemeral pubkey and receive one too.
|
||||
// NOTE: every 32-byte string is accepted as a Curve25519 public key
|
||||
// (see DJB's Curve25519 paper: http://cr.yp.to/ecdh/curve25519-20060209.pdf)
|
||||
remEphPub, err := shareEphPubKey(conn, locEphPub)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Compute common shared secret.
|
||||
shrSecret := computeSharedSecret(remEphPub, locEphPriv)
|
||||
|
||||
// Sort by lexical order.
|
||||
loEphPub, hiEphPub := sort32(locEphPub, remEphPub)
|
||||
|
||||
// Generate nonces to use for secretbox.
|
||||
recvNonce, sendNonce := genNonces(loEphPub, hiEphPub, locEphPub == loEphPub)
|
||||
|
||||
// Generate common challenge to sign.
|
||||
challenge := genChallenge(loEphPub, hiEphPub)
|
||||
|
||||
// Construct SecretConnection.
|
||||
sc := &SecretConnection{
|
||||
conn: conn,
|
||||
recvBuffer: nil,
|
||||
recvNonce: recvNonce,
|
||||
sendNonce: sendNonce,
|
||||
shrSecret: shrSecret,
|
||||
}
|
||||
|
||||
// Sign the challenge bytes for authentication.
|
||||
locSignature := signChallenge(challenge, locPrivKey)
|
||||
|
||||
// Share (in secret) each other's pubkey & challenge signature
|
||||
authSigMsg, err := shareAuthSignature(sc, locPubKey, locSignature)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
remPubKey, remSignature := authSigMsg.Key, authSigMsg.Sig
|
||||
if !remPubKey.VerifyBytes(challenge[:], remSignature) {
|
||||
return nil, errors.New("Challenge verification failed")
|
||||
}
|
||||
|
||||
// We've authorized.
|
||||
sc.remPubKey = remPubKey
|
||||
return sc, nil
|
||||
}
|
||||
|
||||
// Returns authenticated remote pubkey
|
||||
func (sc *SecretConnection) RemotePubKey() acm.PubKeyEd25519 {
|
||||
return sc.remPubKey
|
||||
}
|
||||
|
||||
// Writes encrypted frames of `sealedFrameSize`
|
||||
// CONTRACT: data smaller than dataMaxSize is read atomically.
|
||||
func (sc *SecretConnection) Write(data []byte) (n int, err error) {
|
||||
for 0 < len(data) {
|
||||
var frame []byte = make([]byte, totalFrameSize)
|
||||
var chunk []byte
|
||||
if dataMaxSize < len(data) {
|
||||
chunk = data[:dataMaxSize]
|
||||
data = data[dataMaxSize:]
|
||||
} else {
|
||||
chunk = data
|
||||
data = nil
|
||||
}
|
||||
chunkLength := len(chunk)
|
||||
binary.BigEndian.PutUint16(frame, uint16(chunkLength))
|
||||
copy(frame[dataLenSize:], chunk)
|
||||
|
||||
// encrypt the frame
|
||||
var sealedFrame = make([]byte, sealedFrameSize)
|
||||
secretbox.Seal(sealedFrame[:0], frame, sc.sendNonce, sc.shrSecret)
|
||||
// fmt.Printf("secretbox.Seal(sealed:%X,sendNonce:%X,shrSecret:%X\n", sealedFrame, sc.sendNonce, sc.shrSecret)
|
||||
incr2Nonce(sc.sendNonce)
|
||||
// end encryption
|
||||
|
||||
_, err := sc.conn.Write(sealedFrame)
|
||||
if err != nil {
|
||||
return n, err
|
||||
} else {
|
||||
n += len(chunk)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// CONTRACT: data smaller than dataMaxSize is read atomically.
|
||||
func (sc *SecretConnection) Read(data []byte) (n int, err error) {
|
||||
if 0 < len(sc.recvBuffer) {
|
||||
n_ := copy(data, sc.recvBuffer)
|
||||
sc.recvBuffer = sc.recvBuffer[n_:]
|
||||
return
|
||||
}
|
||||
|
||||
sealedFrame := make([]byte, sealedFrameSize)
|
||||
_, err = io.ReadFull(sc.conn, sealedFrame)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// decrypt the frame
|
||||
var frame = make([]byte, totalFrameSize)
|
||||
// fmt.Printf("secretbox.Open(sealed:%X,recvNonce:%X,shrSecret:%X\n", sealedFrame, sc.recvNonce, sc.shrSecret)
|
||||
_, ok := secretbox.Open(frame[:0], sealedFrame, sc.recvNonce, sc.shrSecret)
|
||||
if !ok {
|
||||
return n, errors.New("Failed to decrypt SecretConnection")
|
||||
}
|
||||
incr2Nonce(sc.recvNonce)
|
||||
// end decryption
|
||||
|
||||
var chunkLength = binary.BigEndian.Uint16(frame) // read the first two bytes
|
||||
if chunkLength > dataMaxSize {
|
||||
return 0, errors.New("chunkLength is greater than dataMaxSize")
|
||||
}
|
||||
var chunk = frame[dataLenSize : dataLenSize+chunkLength]
|
||||
|
||||
n = copy(data, chunk)
|
||||
sc.recvBuffer = chunk[n:]
|
||||
return
|
||||
}
|
||||
|
||||
// Implements net.Conn
|
||||
func (sc *SecretConnection) Close() error { return sc.conn.Close() }
|
||||
func (sc *SecretConnection) LocalAddr() net.Addr { return sc.conn.(net.Conn).LocalAddr() }
|
||||
func (sc *SecretConnection) RemoteAddr() net.Addr { return sc.conn.(net.Conn).RemoteAddr() }
|
||||
func (sc *SecretConnection) SetDeadline(t time.Time) error { return sc.conn.(net.Conn).SetDeadline(t) }
|
||||
func (sc *SecretConnection) SetReadDeadline(t time.Time) error {
|
||||
return sc.conn.(net.Conn).SetReadDeadline(t)
|
||||
}
|
||||
func (sc *SecretConnection) SetWriteDeadline(t time.Time) error {
|
||||
return sc.conn.(net.Conn).SetWriteDeadline(t)
|
||||
}
|
||||
|
||||
func genEphKeys() (ephPub, ephPriv *[32]byte) {
|
||||
var err error
|
||||
ephPub, ephPriv, err = box.GenerateKey(crand.Reader)
|
||||
if err != nil {
|
||||
PanicCrisis("Could not generate ephemeral keypairs")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func shareEphPubKey(conn io.ReadWriteCloser, locEphPub *[32]byte) (remEphPub *[32]byte, err error) {
|
||||
var err1, err2 error
|
||||
|
||||
Parallel(
|
||||
func() {
|
||||
_, err1 = conn.Write(locEphPub[:])
|
||||
},
|
||||
func() {
|
||||
remEphPub = new([32]byte)
|
||||
_, err2 = io.ReadFull(conn, remEphPub[:])
|
||||
},
|
||||
)
|
||||
|
||||
if err1 != nil {
|
||||
return nil, err1
|
||||
}
|
||||
if err2 != nil {
|
||||
return nil, err2
|
||||
}
|
||||
|
||||
return remEphPub, nil
|
||||
}
|
||||
|
||||
func computeSharedSecret(remPubKey, locPrivKey *[32]byte) (shrSecret *[32]byte) {
|
||||
shrSecret = new([32]byte)
|
||||
box.Precompute(shrSecret, remPubKey, locPrivKey)
|
||||
return
|
||||
}
|
||||
|
||||
func sort32(foo, bar *[32]byte) (lo, hi *[32]byte) {
|
||||
if bytes.Compare(foo[:], bar[:]) < 0 {
|
||||
lo = foo
|
||||
hi = bar
|
||||
} else {
|
||||
lo = bar
|
||||
hi = foo
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func genNonces(loPubKey, hiPubKey *[32]byte, locIsLo bool) (recvNonce, sendNonce *[24]byte) {
|
||||
nonce1 := hash24(append(loPubKey[:], hiPubKey[:]...))
|
||||
nonce2 := new([24]byte)
|
||||
copy(nonce2[:], nonce1[:])
|
||||
nonce2[len(nonce2)-1] ^= 0x01
|
||||
if locIsLo {
|
||||
recvNonce = nonce1
|
||||
sendNonce = nonce2
|
||||
} else {
|
||||
recvNonce = nonce2
|
||||
sendNonce = nonce1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func genChallenge(loPubKey, hiPubKey *[32]byte) (challenge *[32]byte) {
|
||||
return hash32(append(loPubKey[:], hiPubKey[:]...))
|
||||
}
|
||||
|
||||
func signChallenge(challenge *[32]byte, locPrivKey acm.PrivKeyEd25519) (signature acm.SignatureEd25519) {
|
||||
signature = locPrivKey.Sign(challenge[:]).(acm.SignatureEd25519)
|
||||
return
|
||||
}
|
||||
|
||||
type authSigMessage struct {
|
||||
Key acm.PubKeyEd25519
|
||||
Sig acm.SignatureEd25519
|
||||
}
|
||||
|
||||
func shareAuthSignature(sc *SecretConnection, pubKey acm.PubKeyEd25519, signature acm.SignatureEd25519) (*authSigMessage, error) {
|
||||
var recvMsg authSigMessage
|
||||
var err1, err2 error
|
||||
|
||||
Parallel(
|
||||
func() {
|
||||
msgBytes := wire.BinaryBytes(authSigMessage{pubKey, signature})
|
||||
_, err1 = sc.Write(msgBytes)
|
||||
},
|
||||
func() {
|
||||
readBuffer := make([]byte, authSigMsgSize)
|
||||
_, err2 = io.ReadFull(sc, readBuffer)
|
||||
if err2 != nil {
|
||||
return
|
||||
}
|
||||
n := int64(0) // not used.
|
||||
recvMsg = wire.ReadBinary(authSigMessage{}, bytes.NewBuffer(readBuffer), &n, &err2).(authSigMessage)
|
||||
})
|
||||
|
||||
if err1 != nil {
|
||||
return nil, err1
|
||||
}
|
||||
if err2 != nil {
|
||||
return nil, err2
|
||||
}
|
||||
|
||||
return &recvMsg, nil
|
||||
}
|
||||
|
||||
func verifyChallengeSignature(challenge *[32]byte, remPubKey acm.PubKeyEd25519, remSignature acm.SignatureEd25519) bool {
|
||||
return remPubKey.VerifyBytes(challenge[:], remSignature)
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
|
||||
// sha256
|
||||
func hash32(input []byte) (res *[32]byte) {
|
||||
hasher := sha256.New()
|
||||
hasher.Write(input) // does not error
|
||||
resSlice := hasher.Sum(nil)
|
||||
res = new([32]byte)
|
||||
copy(res[:], resSlice)
|
||||
return
|
||||
}
|
||||
|
||||
// We only fill in the first 20 bytes with ripemd160
|
||||
func hash24(input []byte) (res *[24]byte) {
|
||||
hasher := ripemd160.New()
|
||||
hasher.Write(input) // does not error
|
||||
resSlice := hasher.Sum(nil)
|
||||
res = new([24]byte)
|
||||
copy(res[:], resSlice)
|
||||
return
|
||||
}
|
||||
|
||||
// ripemd160
|
||||
func hash20(input []byte) (res *[20]byte) {
|
||||
hasher := ripemd160.New()
|
||||
hasher.Write(input) // does not error
|
||||
resSlice := hasher.Sum(nil)
|
||||
res = new([20]byte)
|
||||
copy(res[:], resSlice)
|
||||
return
|
||||
}
|
||||
|
||||
// increment nonce big-endian by 2 with wraparound.
|
||||
func incr2Nonce(nonce *[24]byte) {
|
||||
incrNonce(nonce)
|
||||
incrNonce(nonce)
|
||||
}
|
||||
|
||||
// increment nonce big-endian by 1 with wraparound.
|
||||
func incrNonce(nonce *[24]byte) {
|
||||
for i := 23; 0 <= i; i-- {
|
||||
nonce[i] += 1
|
||||
if nonce[i] != 0 {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,202 +0,0 @@
|
||||
package p2p
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
acm "github.com/tendermint/tendermint/account"
|
||||
. "github.com/tendermint/tendermint/common"
|
||||
)
|
||||
|
||||
type dummyConn struct {
|
||||
*io.PipeReader
|
||||
*io.PipeWriter
|
||||
}
|
||||
|
||||
func (drw dummyConn) Close() (err error) {
|
||||
err2 := drw.PipeWriter.CloseWithError(io.EOF)
|
||||
err1 := drw.PipeReader.Close()
|
||||
if err2 != nil {
|
||||
return err
|
||||
}
|
||||
return err1
|
||||
}
|
||||
|
||||
// Each returned ReadWriteCloser is akin to a net.Connection
|
||||
func makeDummyConnPair() (fooConn, barConn dummyConn) {
|
||||
barReader, fooWriter := io.Pipe()
|
||||
fooReader, barWriter := io.Pipe()
|
||||
return dummyConn{fooReader, fooWriter}, dummyConn{barReader, barWriter}
|
||||
}
|
||||
|
||||
func makeSecretConnPair(tb testing.TB) (fooSecConn, barSecConn *SecretConnection) {
|
||||
fooConn, barConn := makeDummyConnPair()
|
||||
fooPrvKey := acm.GenPrivKeyEd25519()
|
||||
fooPubKey := fooPrvKey.PubKey().(acm.PubKeyEd25519)
|
||||
barPrvKey := acm.GenPrivKeyEd25519()
|
||||
barPubKey := barPrvKey.PubKey().(acm.PubKeyEd25519)
|
||||
|
||||
Parallel(
|
||||
func() {
|
||||
var err error
|
||||
fooSecConn, err = MakeSecretConnection(fooConn, fooPrvKey)
|
||||
if err != nil {
|
||||
tb.Errorf("Failed to establish SecretConnection for foo: %v", err)
|
||||
return
|
||||
}
|
||||
remotePubBytes := fooSecConn.RemotePubKey()
|
||||
if !bytes.Equal(remotePubBytes[:], barPubKey[:]) {
|
||||
tb.Errorf("Unexpected fooSecConn.RemotePubKey. Expected %v, got %v",
|
||||
barPubKey, fooSecConn.RemotePubKey())
|
||||
}
|
||||
},
|
||||
func() {
|
||||
var err error
|
||||
barSecConn, err = MakeSecretConnection(barConn, barPrvKey)
|
||||
if barSecConn == nil {
|
||||
tb.Errorf("Failed to establish SecretConnection for bar: %v", err)
|
||||
return
|
||||
}
|
||||
remotePubBytes := barSecConn.RemotePubKey()
|
||||
if !bytes.Equal(remotePubBytes[:], fooPubKey[:]) {
|
||||
tb.Errorf("Unexpected barSecConn.RemotePubKey. Expected %v, got %v",
|
||||
fooPubKey, barSecConn.RemotePubKey())
|
||||
}
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func TestSecretConnectionHandshake(t *testing.T) {
|
||||
fooSecConn, barSecConn := makeSecretConnPair(t)
|
||||
fooSecConn.Close()
|
||||
barSecConn.Close()
|
||||
}
|
||||
|
||||
func TestSecretConnectionReadWrite(t *testing.T) {
|
||||
fooConn, barConn := makeDummyConnPair()
|
||||
fooWrites, barWrites := []string{}, []string{}
|
||||
fooReads, barReads := []string{}, []string{}
|
||||
|
||||
// Pre-generate the things to write (for foo & bar)
|
||||
for i := 0; i < 100; i++ {
|
||||
fooWrites = append(fooWrites, RandStr((RandInt()%(dataMaxSize*5))+1))
|
||||
barWrites = append(barWrites, RandStr((RandInt()%(dataMaxSize*5))+1))
|
||||
}
|
||||
|
||||
// A helper that will run with (fooConn, fooWrites, fooReads) and vice versa
|
||||
genNodeRunner := func(nodeConn dummyConn, nodeWrites []string, nodeReads *[]string) func() {
|
||||
return func() {
|
||||
// Node handskae
|
||||
nodePrvKey := acm.GenPrivKeyEd25519()
|
||||
nodeSecretConn, err := MakeSecretConnection(nodeConn, nodePrvKey)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to establish SecretConnection for node: %v", err)
|
||||
return
|
||||
}
|
||||
// In parallel, handle reads and writes
|
||||
Parallel(
|
||||
func() {
|
||||
// Node writes
|
||||
for _, nodeWrite := range nodeWrites {
|
||||
n, err := nodeSecretConn.Write([]byte(nodeWrite))
|
||||
if err != nil {
|
||||
t.Errorf("Failed to write to nodeSecretConn: %v", err)
|
||||
return
|
||||
}
|
||||
if n != len(nodeWrite) {
|
||||
t.Errorf("Failed to write all bytes. Expected %v, wrote %v", len(nodeWrite), n)
|
||||
return
|
||||
}
|
||||
}
|
||||
nodeConn.PipeWriter.Close()
|
||||
},
|
||||
func() {
|
||||
// Node reads
|
||||
readBuffer := make([]byte, dataMaxSize)
|
||||
for {
|
||||
n, err := nodeSecretConn.Read(readBuffer)
|
||||
if err == io.EOF {
|
||||
return
|
||||
} else if err != nil {
|
||||
t.Errorf("Failed to read from nodeSecretConn: %v", err)
|
||||
return
|
||||
}
|
||||
*nodeReads = append(*nodeReads, string(readBuffer[:n]))
|
||||
}
|
||||
nodeConn.PipeReader.Close()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Run foo & bar in parallel
|
||||
Parallel(
|
||||
genNodeRunner(fooConn, fooWrites, &fooReads),
|
||||
genNodeRunner(barConn, barWrites, &barReads),
|
||||
)
|
||||
|
||||
// A helper to ensure that the writes and reads match.
|
||||
// Additionally, small writes (<= dataMaxSize) must be atomically read.
|
||||
compareWritesReads := func(writes []string, reads []string) {
|
||||
for {
|
||||
// Pop next write & corresponding reads
|
||||
var read, write string = "", writes[0]
|
||||
var readCount = 0
|
||||
for _, readChunk := range reads {
|
||||
read += readChunk
|
||||
readCount += 1
|
||||
if len(write) <= len(read) {
|
||||
break
|
||||
}
|
||||
if len(write) <= dataMaxSize {
|
||||
break // atomicity of small writes
|
||||
}
|
||||
}
|
||||
// Compare
|
||||
if write != read {
|
||||
t.Errorf("Expected to read %X, got %X", write, read)
|
||||
}
|
||||
// Iterate
|
||||
writes = writes[1:]
|
||||
reads = reads[readCount:]
|
||||
if len(writes) == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
compareWritesReads(fooWrites, barReads)
|
||||
compareWritesReads(barWrites, fooReads)
|
||||
|
||||
}
|
||||
|
||||
func BenchmarkSecretConnection(b *testing.B) {
|
||||
b.StopTimer()
|
||||
fooSecConn, barSecConn := makeSecretConnPair(b)
|
||||
fooWriteText := RandStr(dataMaxSize)
|
||||
// Consume reads from bar's reader
|
||||
go func() {
|
||||
readBuffer := make([]byte, dataMaxSize)
|
||||
for {
|
||||
_, err := barSecConn.Read(readBuffer)
|
||||
if err == io.EOF {
|
||||
return
|
||||
} else if err != nil {
|
||||
b.Fatalf("Failed to read from barSecConn: %v", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := fooSecConn.Write([]byte(fooWriteText))
|
||||
if err != nil {
|
||||
b.Fatalf("Failed to write to fooSecConn: %v", err)
|
||||
}
|
||||
}
|
||||
b.StopTimer()
|
||||
|
||||
fooSecConn.Close()
|
||||
//barSecConn.Close() race condition
|
||||
}
|
||||
-391
@@ -1,391 +0,0 @@
|
||||
package p2p
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/tendermint/tendermint/Godeps/_workspace/src/github.com/tendermint/log15"
|
||||
acm "github.com/tendermint/tendermint/account"
|
||||
. "github.com/tendermint/tendermint/common"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
type Reactor interface {
|
||||
Service // Start, Stop
|
||||
|
||||
SetSwitch(*Switch)
|
||||
GetChannels() []*ChannelDescriptor
|
||||
AddPeer(peer *Peer)
|
||||
RemovePeer(peer *Peer, reason interface{})
|
||||
Receive(chID byte, peer *Peer, msgBytes []byte)
|
||||
}
|
||||
|
||||
//--------------------------------------
|
||||
|
||||
type BaseReactor struct {
|
||||
QuitService // Provides Start, Stop, .Quit
|
||||
Switch *Switch
|
||||
}
|
||||
|
||||
func NewBaseReactor(log log15.Logger, name string, impl Reactor) *BaseReactor {
|
||||
return &BaseReactor{
|
||||
QuitService: *NewQuitService(log, name, impl),
|
||||
Switch: nil,
|
||||
}
|
||||
}
|
||||
|
||||
func (br *BaseReactor) SetSwitch(sw *Switch) {
|
||||
br.Switch = sw
|
||||
}
|
||||
func (_ *BaseReactor) GetChannels() []*ChannelDescriptor { return nil }
|
||||
func (_ *BaseReactor) AddPeer(peer *Peer) {}
|
||||
func (_ *BaseReactor) RemovePeer(peer *Peer, reason interface{}) {}
|
||||
func (_ *BaseReactor) Receive(chID byte, peer *Peer, msgBytes []byte) {}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/*
|
||||
The `Switch` handles peer connections and exposes an API to receive incoming messages
|
||||
on `Reactors`. Each `Reactor` is responsible for handling incoming messages of one
|
||||
or more `Channels`. So while sending outgoing messages is typically performed on the peer,
|
||||
incoming messages are received on the reactor.
|
||||
*/
|
||||
type Switch struct {
|
||||
BaseService
|
||||
|
||||
listeners []Listener
|
||||
reactors map[string]Reactor
|
||||
chDescs []*ChannelDescriptor
|
||||
reactorsByCh map[byte]Reactor
|
||||
peers *PeerSet
|
||||
dialing *CMap
|
||||
nodeInfo *types.NodeInfo // our node info
|
||||
nodePrivKey acm.PrivKeyEd25519 // our node privkey
|
||||
}
|
||||
|
||||
var (
|
||||
ErrSwitchDuplicatePeer = errors.New("Duplicate peer")
|
||||
ErrSwitchMaxPeersPerIPRange = errors.New("IP range has too many peers")
|
||||
)
|
||||
|
||||
const (
|
||||
peerDialTimeoutSeconds = 3 // TODO make this configurable
|
||||
handshakeTimeoutSeconds = 20 // TODO make this configurable
|
||||
maxNumPeers = 50 // TODO make this configurable
|
||||
)
|
||||
|
||||
func NewSwitch() *Switch {
|
||||
sw := &Switch{
|
||||
reactors: make(map[string]Reactor),
|
||||
chDescs: make([]*ChannelDescriptor, 0),
|
||||
reactorsByCh: make(map[byte]Reactor),
|
||||
peers: NewPeerSet(),
|
||||
dialing: NewCMap(),
|
||||
nodeInfo: nil,
|
||||
}
|
||||
sw.BaseService = *NewBaseService(log, "P2P Switch", sw)
|
||||
return sw
|
||||
}
|
||||
|
||||
// Not goroutine safe.
|
||||
func (sw *Switch) AddReactor(name string, reactor Reactor) Reactor {
|
||||
// Validate the reactor.
|
||||
// No two reactors can share the same channel.
|
||||
reactorChannels := reactor.GetChannels()
|
||||
for _, chDesc := range reactorChannels {
|
||||
chID := chDesc.ID
|
||||
if sw.reactorsByCh[chID] != nil {
|
||||
PanicSanity(fmt.Sprintf("Channel %X has multiple reactors %v & %v", chID, sw.reactorsByCh[chID], reactor))
|
||||
}
|
||||
sw.chDescs = append(sw.chDescs, chDesc)
|
||||
sw.reactorsByCh[chID] = reactor
|
||||
}
|
||||
sw.reactors[name] = reactor
|
||||
reactor.SetSwitch(sw)
|
||||
return reactor
|
||||
}
|
||||
|
||||
// Not goroutine safe.
|
||||
func (sw *Switch) Reactors() map[string]Reactor {
|
||||
return sw.reactors
|
||||
}
|
||||
|
||||
// Not goroutine safe.
|
||||
func (sw *Switch) Reactor(name string) Reactor {
|
||||
return sw.reactors[name]
|
||||
}
|
||||
|
||||
// Not goroutine safe.
|
||||
func (sw *Switch) AddListener(l Listener) {
|
||||
sw.listeners = append(sw.listeners, l)
|
||||
}
|
||||
|
||||
// Not goroutine safe.
|
||||
func (sw *Switch) Listeners() []Listener {
|
||||
return sw.listeners
|
||||
}
|
||||
|
||||
// Not goroutine safe.
|
||||
func (sw *Switch) IsListening() bool {
|
||||
return len(sw.listeners) > 0
|
||||
}
|
||||
|
||||
// Not goroutine safe.
|
||||
func (sw *Switch) SetNodeInfo(nodeInfo *types.NodeInfo) {
|
||||
sw.nodeInfo = nodeInfo
|
||||
}
|
||||
|
||||
// Not goroutine safe.
|
||||
func (sw *Switch) NodeInfo() *types.NodeInfo {
|
||||
return sw.nodeInfo
|
||||
}
|
||||
|
||||
// Not goroutine safe.
|
||||
// NOTE: Overwrites sw.nodeInfo.PubKey
|
||||
func (sw *Switch) SetNodePrivKey(nodePrivKey acm.PrivKeyEd25519) {
|
||||
sw.nodePrivKey = nodePrivKey
|
||||
if sw.nodeInfo != nil {
|
||||
sw.nodeInfo.PubKey = nodePrivKey.PubKey().(acm.PubKeyEd25519)
|
||||
}
|
||||
}
|
||||
|
||||
// Switch.Start() starts all the reactors, peers, and listeners.
|
||||
func (sw *Switch) OnStart() error {
|
||||
sw.BaseService.OnStart()
|
||||
// Start reactors
|
||||
for _, reactor := range sw.reactors {
|
||||
_, err := reactor.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Start peers
|
||||
for _, peer := range sw.peers.List() {
|
||||
sw.startInitPeer(peer)
|
||||
}
|
||||
// Start listeners
|
||||
for _, listener := range sw.listeners {
|
||||
go sw.listenerRoutine(listener)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sw *Switch) OnStop() {
|
||||
sw.BaseService.OnStop()
|
||||
// Stop listeners
|
||||
for _, listener := range sw.listeners {
|
||||
listener.Stop()
|
||||
}
|
||||
sw.listeners = nil
|
||||
// Stop peers
|
||||
for _, peer := range sw.peers.List() {
|
||||
peer.Stop()
|
||||
}
|
||||
sw.peers = NewPeerSet()
|
||||
// Stop reactors
|
||||
for _, reactor := range sw.reactors {
|
||||
reactor.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: This performs a blocking handshake before the peer is added.
|
||||
// CONTRACT: Iff error is returned, peer is nil, and conn is immediately closed.
|
||||
func (sw *Switch) AddPeerWithConnection(conn net.Conn, outbound bool) (*Peer, error) {
|
||||
// Set deadline for handshake so we don't block forever on conn.ReadFull
|
||||
conn.SetDeadline(time.Now().Add(handshakeTimeoutSeconds * time.Second))
|
||||
|
||||
// First, encrypt the connection.
|
||||
sconn, err := MakeSecretConnection(conn, sw.nodePrivKey)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
// Then, perform node handshake
|
||||
peerNodeInfo, err := peerHandshake(sconn, sw.nodeInfo)
|
||||
if err != nil {
|
||||
sconn.Close()
|
||||
return nil, err
|
||||
}
|
||||
// Check that the professed PubKey matches the sconn's.
|
||||
if !peerNodeInfo.PubKey.Equals(sconn.RemotePubKey()) {
|
||||
sconn.Close()
|
||||
return nil, fmt.Errorf("Ignoring connection with unmatching pubkey: %v vs %v",
|
||||
peerNodeInfo.PubKey, sconn.RemotePubKey())
|
||||
}
|
||||
// Avoid self
|
||||
if peerNodeInfo.PubKey.Equals(sw.nodeInfo.PubKey) {
|
||||
sconn.Close()
|
||||
return nil, fmt.Errorf("Ignoring connection from self")
|
||||
}
|
||||
// Check version, chain id
|
||||
if err := sw.nodeInfo.CompatibleWith(peerNodeInfo); err != nil {
|
||||
sconn.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// The peerNodeInfo is not verified, so overwrite
|
||||
// the IP, and the port too if we dialed out
|
||||
// Everything else we just have to trust
|
||||
ip, port, _ := net.SplitHostPort(sconn.RemoteAddr().String())
|
||||
peerNodeInfo.Host = ip
|
||||
if outbound {
|
||||
porti, _ := strconv.Atoi(port)
|
||||
peerNodeInfo.P2PPort = uint16(porti)
|
||||
}
|
||||
peer := newPeer(sconn, peerNodeInfo, outbound, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError)
|
||||
|
||||
// Add the peer to .peers
|
||||
// ignore if duplicate or if we already have too many for that IP range
|
||||
if err := sw.peers.Add(peer); err != nil {
|
||||
log.Notice("Ignoring peer", "error", err, "peer", peer)
|
||||
peer.Stop()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// remove deadline and start peer
|
||||
conn.SetDeadline(time.Time{})
|
||||
if sw.IsRunning() {
|
||||
sw.startInitPeer(peer)
|
||||
}
|
||||
|
||||
log.Notice("Added peer", "peer", peer)
|
||||
return peer, nil
|
||||
}
|
||||
|
||||
func (sw *Switch) startInitPeer(peer *Peer) {
|
||||
peer.Start() // spawn send/recv routines
|
||||
sw.addPeerToReactors(peer) // run AddPeer on each reactor
|
||||
}
|
||||
|
||||
func (sw *Switch) DialPeerWithAddress(addr *NetAddress) (*Peer, error) {
|
||||
log.Info("Dialing address", "address", addr)
|
||||
sw.dialing.Set(addr.IP.String(), addr)
|
||||
conn, err := addr.DialTimeout(peerDialTimeoutSeconds * time.Second)
|
||||
sw.dialing.Delete(addr.IP.String())
|
||||
if err != nil {
|
||||
log.Info("Failed dialing address", "address", addr, "error", err)
|
||||
return nil, err
|
||||
}
|
||||
peer, err := sw.AddPeerWithConnection(conn, true)
|
||||
if err != nil {
|
||||
log.Info("Failed adding peer", "address", addr, "conn", conn, "error", err)
|
||||
return nil, err
|
||||
}
|
||||
log.Notice("Dialed and added peer", "address", addr, "peer", peer)
|
||||
return peer, nil
|
||||
}
|
||||
|
||||
func (sw *Switch) IsDialing(addr *NetAddress) bool {
|
||||
return sw.dialing.Has(addr.IP.String())
|
||||
}
|
||||
|
||||
// Broadcast runs a go routine for each attempted send, which will block
|
||||
// trying to send for defaultSendTimeoutSeconds. Returns a channel
|
||||
// which receives success values for each attempted send (false if times out)
|
||||
func (sw *Switch) Broadcast(chID byte, msg interface{}) chan bool {
|
||||
successChan := make(chan bool, len(sw.peers.List()))
|
||||
log.Info("Broadcast", "channel", chID, "msg", msg)
|
||||
for _, peer := range sw.peers.List() {
|
||||
go func(peer *Peer) {
|
||||
success := peer.Send(chID, msg)
|
||||
successChan <- success
|
||||
}(peer)
|
||||
}
|
||||
return successChan
|
||||
}
|
||||
|
||||
// Returns the count of outbound/inbound and outbound-dialing peers.
|
||||
func (sw *Switch) NumPeers() (outbound, inbound, dialing int) {
|
||||
peers := sw.peers.List()
|
||||
for _, peer := range peers {
|
||||
if peer.outbound {
|
||||
outbound++
|
||||
} else {
|
||||
inbound++
|
||||
}
|
||||
}
|
||||
dialing = sw.dialing.Size()
|
||||
return
|
||||
}
|
||||
|
||||
func (sw *Switch) Peers() IPeerSet {
|
||||
return sw.peers
|
||||
}
|
||||
|
||||
// Disconnect from a peer due to external error.
|
||||
// TODO: make record depending on reason.
|
||||
func (sw *Switch) StopPeerForError(peer *Peer, reason interface{}) {
|
||||
log.Notice("Stopping peer for error", "peer", peer, "error", reason)
|
||||
sw.peers.Remove(peer)
|
||||
peer.Stop()
|
||||
sw.removePeerFromReactors(peer, reason)
|
||||
}
|
||||
|
||||
// Disconnect from a peer gracefully.
|
||||
// TODO: handle graceful disconnects.
|
||||
func (sw *Switch) StopPeerGracefully(peer *Peer) {
|
||||
log.Notice("Stopping peer gracefully")
|
||||
sw.peers.Remove(peer)
|
||||
peer.Stop()
|
||||
sw.removePeerFromReactors(peer, nil)
|
||||
}
|
||||
|
||||
func (sw *Switch) addPeerToReactors(peer *Peer) {
|
||||
for _, reactor := range sw.reactors {
|
||||
reactor.AddPeer(peer)
|
||||
}
|
||||
}
|
||||
|
||||
func (sw *Switch) removePeerFromReactors(peer *Peer, reason interface{}) {
|
||||
for _, reactor := range sw.reactors {
|
||||
reactor.RemovePeer(peer, reason)
|
||||
}
|
||||
}
|
||||
|
||||
func (sw *Switch) listenerRoutine(l Listener) {
|
||||
for {
|
||||
inConn, ok := <-l.Connections()
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
|
||||
// ignore connection if we already have enough
|
||||
if maxNumPeers <= sw.peers.Size() {
|
||||
log.Info("Ignoring inbound connection: already have enough peers", "address", inConn.RemoteAddr().String(), "numPeers", sw.peers.Size(), "max", maxNumPeers)
|
||||
continue
|
||||
}
|
||||
|
||||
// Ignore connections from IP ranges for which we have too many
|
||||
if sw.peers.HasMaxForIPRange(inConn) {
|
||||
log.Info("Ignoring inbound connection: already have enough peers for that IP range", "address", inConn.RemoteAddr().String())
|
||||
continue
|
||||
}
|
||||
|
||||
// New inbound connection!
|
||||
_, err := sw.AddPeerWithConnection(inConn, false)
|
||||
if err != nil {
|
||||
log.Notice("Ignoring inbound connection: error on AddPeerWithConnection", "address", inConn.RemoteAddr().String(), "error", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// NOTE: We don't yet have the listening port of the
|
||||
// remote (if they have a listener at all).
|
||||
// The peerHandshake will handle that
|
||||
}
|
||||
|
||||
// cleanup
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
type SwitchEventNewPeer struct {
|
||||
Peer *Peer
|
||||
}
|
||||
|
||||
type SwitchEventDonePeer struct {
|
||||
Peer *Peer
|
||||
Error interface{}
|
||||
}
|
||||
@@ -1,236 +0,0 @@
|
||||
package p2p
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
acm "github.com/tendermint/tendermint/account"
|
||||
. "github.com/tendermint/tendermint/common"
|
||||
_ "github.com/tendermint/tendermint/config/tendermint_test"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
"github.com/tendermint/tendermint/wire"
|
||||
)
|
||||
|
||||
type PeerMessage struct {
|
||||
PeerKey string
|
||||
Bytes []byte
|
||||
Counter int
|
||||
}
|
||||
|
||||
type TestReactor struct {
|
||||
BaseReactor
|
||||
|
||||
mtx sync.Mutex
|
||||
channels []*ChannelDescriptor
|
||||
peersAdded []*Peer
|
||||
peersRemoved []*Peer
|
||||
logMessages bool
|
||||
msgsCounter int
|
||||
msgsReceived map[byte][]PeerMessage
|
||||
}
|
||||
|
||||
func NewTestReactor(channels []*ChannelDescriptor, logMessages bool) *TestReactor {
|
||||
tr := &TestReactor{
|
||||
channels: channels,
|
||||
logMessages: logMessages,
|
||||
msgsReceived: make(map[byte][]PeerMessage),
|
||||
}
|
||||
tr.BaseReactor = *NewBaseReactor(log, "TestReactor", tr)
|
||||
return tr
|
||||
}
|
||||
|
||||
func (tr *TestReactor) GetChannels() []*ChannelDescriptor {
|
||||
return tr.channels
|
||||
}
|
||||
|
||||
func (tr *TestReactor) AddPeer(peer *Peer) {
|
||||
tr.mtx.Lock()
|
||||
defer tr.mtx.Unlock()
|
||||
tr.peersAdded = append(tr.peersAdded, peer)
|
||||
}
|
||||
|
||||
func (tr *TestReactor) RemovePeer(peer *Peer, reason interface{}) {
|
||||
tr.mtx.Lock()
|
||||
defer tr.mtx.Unlock()
|
||||
tr.peersRemoved = append(tr.peersRemoved, peer)
|
||||
}
|
||||
|
||||
func (tr *TestReactor) Receive(chID byte, peer *Peer, msgBytes []byte) {
|
||||
if tr.logMessages {
|
||||
tr.mtx.Lock()
|
||||
defer tr.mtx.Unlock()
|
||||
//fmt.Printf("Received: %X, %X\n", chID, msgBytes)
|
||||
tr.msgsReceived[chID] = append(tr.msgsReceived[chID], PeerMessage{peer.Key, msgBytes, tr.msgsCounter})
|
||||
tr.msgsCounter++
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// convenience method for creating two switches connected to each other.
|
||||
func makeSwitchPair(t testing.TB, initSwitch func(*Switch) *Switch) (*Switch, *Switch) {
|
||||
|
||||
s1PrivKey := acm.GenPrivKeyEd25519()
|
||||
s2PrivKey := acm.GenPrivKeyEd25519()
|
||||
|
||||
// Create two switches that will be interconnected.
|
||||
s1 := initSwitch(NewSwitch())
|
||||
s1.SetNodeInfo(&types.NodeInfo{
|
||||
PubKey: s1PrivKey.PubKey().(acm.PubKeyEd25519),
|
||||
Moniker: "switch1",
|
||||
ChainID: "testing",
|
||||
Version: types.Versions{Tendermint: "123.123.123"},
|
||||
})
|
||||
s1.SetNodePrivKey(s1PrivKey)
|
||||
s2 := initSwitch(NewSwitch())
|
||||
s2.SetNodeInfo(&types.NodeInfo{
|
||||
PubKey: s2PrivKey.PubKey().(acm.PubKeyEd25519),
|
||||
Moniker: "switch2",
|
||||
ChainID: "testing",
|
||||
Version: types.Versions{Tendermint: "123.123.123"},
|
||||
})
|
||||
s2.SetNodePrivKey(s2PrivKey)
|
||||
|
||||
// Start switches and reactors
|
||||
s1.Start()
|
||||
s2.Start()
|
||||
|
||||
// Create a listener for s1
|
||||
l := NewDefaultListener("tcp", ":8001")
|
||||
|
||||
// Dial the listener & add the connection to s2.
|
||||
lAddr := l.ExternalAddress()
|
||||
connOut, err := lAddr.Dial()
|
||||
if err != nil {
|
||||
t.Fatalf("Could not connect to listener address %v", lAddr)
|
||||
} else {
|
||||
t.Logf("Created a connection to listener address %v", lAddr)
|
||||
}
|
||||
connIn, ok := <-l.Connections()
|
||||
if !ok {
|
||||
t.Fatalf("Could not get inbound connection from listener")
|
||||
}
|
||||
|
||||
go s1.AddPeerWithConnection(connIn, false) // AddPeer is blocking, requires handshake.
|
||||
s2.AddPeerWithConnection(connOut, true)
|
||||
|
||||
// Wait for things to happen, peers to get added...
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
// Close the server, no longer needed.
|
||||
l.Stop()
|
||||
|
||||
return s1, s2
|
||||
}
|
||||
|
||||
func TestSwitches(t *testing.T) {
|
||||
s1, s2 := makeSwitchPair(t, func(sw *Switch) *Switch {
|
||||
// Make two reactors of two channels each
|
||||
sw.AddReactor("foo", NewTestReactor([]*ChannelDescriptor{
|
||||
&ChannelDescriptor{ID: byte(0x00), Priority: 10},
|
||||
&ChannelDescriptor{ID: byte(0x01), Priority: 10},
|
||||
}, true))
|
||||
sw.AddReactor("bar", NewTestReactor([]*ChannelDescriptor{
|
||||
&ChannelDescriptor{ID: byte(0x02), Priority: 10},
|
||||
&ChannelDescriptor{ID: byte(0x03), Priority: 10},
|
||||
}, true))
|
||||
return sw
|
||||
})
|
||||
defer s1.Stop()
|
||||
defer s2.Stop()
|
||||
|
||||
// Lets send a message from s1 to s2.
|
||||
if s1.Peers().Size() != 1 {
|
||||
t.Errorf("Expected exactly 1 peer in s1, got %v", s1.Peers().Size())
|
||||
}
|
||||
if s2.Peers().Size() != 1 {
|
||||
t.Errorf("Expected exactly 1 peer in s2, got %v", s2.Peers().Size())
|
||||
}
|
||||
|
||||
ch0Msg := "channel zero"
|
||||
ch1Msg := "channel foo"
|
||||
ch2Msg := "channel bar"
|
||||
|
||||
s1.Broadcast(byte(0x00), ch0Msg)
|
||||
s1.Broadcast(byte(0x01), ch1Msg)
|
||||
s1.Broadcast(byte(0x02), ch2Msg)
|
||||
|
||||
// Wait for things to settle...
|
||||
time.Sleep(5000 * time.Millisecond)
|
||||
|
||||
// Check message on ch0
|
||||
ch0Msgs := s2.Reactor("foo").(*TestReactor).msgsReceived[byte(0x00)]
|
||||
if len(ch0Msgs) != 1 {
|
||||
t.Errorf("Expected to have received 1 message in ch0")
|
||||
}
|
||||
if !bytes.Equal(ch0Msgs[0].Bytes, wire.BinaryBytes(ch0Msg)) {
|
||||
t.Errorf("Unexpected message bytes. Wanted: %X, Got: %X", wire.BinaryBytes(ch0Msg), ch0Msgs[0].Bytes)
|
||||
}
|
||||
|
||||
// Check message on ch1
|
||||
ch1Msgs := s2.Reactor("foo").(*TestReactor).msgsReceived[byte(0x01)]
|
||||
if len(ch1Msgs) != 1 {
|
||||
t.Errorf("Expected to have received 1 message in ch1")
|
||||
}
|
||||
if !bytes.Equal(ch1Msgs[0].Bytes, wire.BinaryBytes(ch1Msg)) {
|
||||
t.Errorf("Unexpected message bytes. Wanted: %X, Got: %X", wire.BinaryBytes(ch1Msg), ch1Msgs[0].Bytes)
|
||||
}
|
||||
|
||||
// Check message on ch2
|
||||
ch2Msgs := s2.Reactor("bar").(*TestReactor).msgsReceived[byte(0x02)]
|
||||
if len(ch2Msgs) != 1 {
|
||||
t.Errorf("Expected to have received 1 message in ch2")
|
||||
}
|
||||
if !bytes.Equal(ch2Msgs[0].Bytes, wire.BinaryBytes(ch2Msg)) {
|
||||
t.Errorf("Unexpected message bytes. Wanted: %X, Got: %X", wire.BinaryBytes(ch2Msg), ch2Msgs[0].Bytes)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func BenchmarkSwitches(b *testing.B) {
|
||||
|
||||
b.StopTimer()
|
||||
|
||||
s1, s2 := makeSwitchPair(b, func(sw *Switch) *Switch {
|
||||
// Make bar reactors of bar channels each
|
||||
sw.AddReactor("foo", NewTestReactor([]*ChannelDescriptor{
|
||||
&ChannelDescriptor{ID: byte(0x00), Priority: 10},
|
||||
&ChannelDescriptor{ID: byte(0x01), Priority: 10},
|
||||
}, false))
|
||||
sw.AddReactor("bar", NewTestReactor([]*ChannelDescriptor{
|
||||
&ChannelDescriptor{ID: byte(0x02), Priority: 10},
|
||||
&ChannelDescriptor{ID: byte(0x03), Priority: 10},
|
||||
}, false))
|
||||
return sw
|
||||
})
|
||||
defer s1.Stop()
|
||||
defer s2.Stop()
|
||||
|
||||
// Allow time for goroutines to boot up
|
||||
time.Sleep(1000 * time.Millisecond)
|
||||
b.StartTimer()
|
||||
|
||||
numSuccess, numFailure := 0, 0
|
||||
|
||||
// Send random message from foo channel to another
|
||||
for i := 0; i < b.N; i++ {
|
||||
chID := byte(i % 4)
|
||||
successChan := s1.Broadcast(chID, "test data")
|
||||
for s := range successChan {
|
||||
if s {
|
||||
numSuccess += 1
|
||||
} else {
|
||||
numFailure += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.Warn(Fmt("success: %v, failure: %v", numSuccess, numFailure))
|
||||
|
||||
// Allow everything to flush before stopping switches & closing connections.
|
||||
b.StopTimer()
|
||||
time.Sleep(1000 * time.Millisecond)
|
||||
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
# `tendermint/p2p/upnp`
|
||||
|
||||
## Resources
|
||||
|
||||
* http://www.upnp-hacks.org/upnp.html
|
||||
@@ -1,7 +0,0 @@
|
||||
package upnp
|
||||
|
||||
import (
|
||||
"github.com/tendermint/tendermint/logger"
|
||||
)
|
||||
|
||||
var log = logger.New("module", "upnp")
|
||||
@@ -1,111 +0,0 @@
|
||||
package upnp
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
. "github.com/tendermint/tendermint/common"
|
||||
)
|
||||
|
||||
type UPNPCapabilities struct {
|
||||
PortMapping bool
|
||||
Hairpin bool
|
||||
}
|
||||
|
||||
func makeUPNPListener(intPort int, extPort int) (NAT, net.Listener, net.IP, error) {
|
||||
nat, err := Discover()
|
||||
if err != nil {
|
||||
return nil, nil, nil, errors.New(fmt.Sprintf("NAT upnp could not be discovered: %v", err))
|
||||
}
|
||||
log.Info(Fmt("ourIP: %v", nat.(*upnpNAT).ourIP))
|
||||
|
||||
ext, err := nat.GetExternalAddress()
|
||||
if err != nil {
|
||||
return nat, nil, nil, errors.New(fmt.Sprintf("External address error: %v", err))
|
||||
}
|
||||
log.Info(Fmt("External address: %v", ext))
|
||||
|
||||
port, err := nat.AddPortMapping("tcp", extPort, intPort, "Tendermint UPnP Probe", 0)
|
||||
if err != nil {
|
||||
return nat, nil, ext, errors.New(fmt.Sprintf("Port mapping error: %v", err))
|
||||
}
|
||||
log.Info(Fmt("Port mapping mapped: %v", port))
|
||||
|
||||
// also run the listener, open for all remote addresses.
|
||||
listener, err := net.Listen("tcp", fmt.Sprintf(":%v", intPort))
|
||||
if err != nil {
|
||||
return nat, nil, ext, errors.New(fmt.Sprintf("Error establishing listener: %v", err))
|
||||
}
|
||||
return nat, listener, ext, nil
|
||||
}
|
||||
|
||||
func testHairpin(listener net.Listener, extAddr string) (supportsHairpin bool) {
|
||||
// Listener
|
||||
go func() {
|
||||
inConn, err := listener.Accept()
|
||||
if err != nil {
|
||||
log.Notice(Fmt("Listener.Accept() error: %v", err))
|
||||
return
|
||||
}
|
||||
log.Info(Fmt("Accepted incoming connection: %v -> %v", inConn.LocalAddr(), inConn.RemoteAddr()))
|
||||
buf := make([]byte, 1024)
|
||||
n, err := inConn.Read(buf)
|
||||
if err != nil {
|
||||
log.Notice(Fmt("Incoming connection read error: %v", err))
|
||||
return
|
||||
}
|
||||
log.Info(Fmt("Incoming connection read %v bytes: %X", n, buf))
|
||||
if string(buf) == "test data" {
|
||||
supportsHairpin = true
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
// Establish outgoing
|
||||
outConn, err := net.Dial("tcp", extAddr)
|
||||
if err != nil {
|
||||
log.Notice(Fmt("Outgoing connection dial error: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
n, err := outConn.Write([]byte("test data"))
|
||||
if err != nil {
|
||||
log.Notice(Fmt("Outgoing connection write error: %v", err))
|
||||
return
|
||||
}
|
||||
log.Info(Fmt("Outgoing connection wrote %v bytes", n))
|
||||
|
||||
// Wait for data receipt
|
||||
time.Sleep(1 * time.Second)
|
||||
return
|
||||
}
|
||||
|
||||
func Probe() (caps UPNPCapabilities, err error) {
|
||||
log.Info("Probing for UPnP!")
|
||||
|
||||
intPort, extPort := 8001, 8001
|
||||
|
||||
nat, listener, ext, err := makeUPNPListener(intPort, extPort)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
caps.PortMapping = true
|
||||
|
||||
// Deferred cleanup
|
||||
defer func() {
|
||||
err = nat.DeletePortMapping("tcp", intPort, extPort)
|
||||
if err != nil {
|
||||
log.Warn(Fmt("Port mapping delete error: %v", err))
|
||||
}
|
||||
listener.Close()
|
||||
}()
|
||||
|
||||
supportsHairpin := testHairpin(listener, fmt.Sprintf("%v:%v", ext, extPort))
|
||||
if supportsHairpin {
|
||||
caps.Hairpin = true
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
@@ -1,380 +0,0 @@
|
||||
/*
|
||||
Taken from taipei-torrent
|
||||
|
||||
Just enough UPnP to be able to forward ports
|
||||
*/
|
||||
package upnp
|
||||
|
||||
// BUG(jae): TODO: use syscalls to get actual ourIP. http://pastebin.com/9exZG4rh
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type upnpNAT struct {
|
||||
serviceURL string
|
||||
ourIP string
|
||||
urnDomain string
|
||||
}
|
||||
|
||||
// protocol is either "udp" or "tcp"
|
||||
type NAT interface {
|
||||
GetExternalAddress() (addr net.IP, err error)
|
||||
AddPortMapping(protocol string, externalPort, internalPort int, description string, timeout int) (mappedExternalPort int, err error)
|
||||
DeletePortMapping(protocol string, externalPort, internalPort int) (err error)
|
||||
}
|
||||
|
||||
func Discover() (nat NAT, err error) {
|
||||
ssdp, err := net.ResolveUDPAddr("udp4", "239.255.255.250:1900")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
conn, err := net.ListenPacket("udp4", ":0")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
socket := conn.(*net.UDPConn)
|
||||
defer socket.Close()
|
||||
|
||||
err = socket.SetDeadline(time.Now().Add(3 * time.Second))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
st := "InternetGatewayDevice:1"
|
||||
|
||||
buf := bytes.NewBufferString(
|
||||
"M-SEARCH * HTTP/1.1\r\n" +
|
||||
"HOST: 239.255.255.250:1900\r\n" +
|
||||
"ST: ssdp:all\r\n" +
|
||||
"MAN: \"ssdp:discover\"\r\n" +
|
||||
"MX: 2\r\n\r\n")
|
||||
message := buf.Bytes()
|
||||
answerBytes := make([]byte, 1024)
|
||||
for i := 0; i < 3; i++ {
|
||||
_, err = socket.WriteToUDP(message, ssdp)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var n int
|
||||
n, _, err = socket.ReadFromUDP(answerBytes)
|
||||
for {
|
||||
n, _, err = socket.ReadFromUDP(answerBytes)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
answer := string(answerBytes[0:n])
|
||||
if strings.Index(answer, st) < 0 {
|
||||
continue
|
||||
}
|
||||
// HTTP header field names are case-insensitive.
|
||||
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
|
||||
locString := "\r\nlocation:"
|
||||
answer = strings.ToLower(answer)
|
||||
locIndex := strings.Index(answer, locString)
|
||||
if locIndex < 0 {
|
||||
continue
|
||||
}
|
||||
loc := answer[locIndex+len(locString):]
|
||||
endIndex := strings.Index(loc, "\r\n")
|
||||
if endIndex < 0 {
|
||||
continue
|
||||
}
|
||||
locURL := strings.TrimSpace(loc[0:endIndex])
|
||||
var serviceURL, urnDomain string
|
||||
serviceURL, urnDomain, err = getServiceURL(locURL)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var ourIP net.IP
|
||||
ourIP, err = localIPv4()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
nat = &upnpNAT{serviceURL: serviceURL, ourIP: ourIP.String(), urnDomain: urnDomain}
|
||||
return
|
||||
}
|
||||
}
|
||||
err = errors.New("UPnP port discovery failed.")
|
||||
return
|
||||
}
|
||||
|
||||
type Envelope struct {
|
||||
XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Envelope"`
|
||||
Soap *SoapBody
|
||||
}
|
||||
type SoapBody struct {
|
||||
XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Body"`
|
||||
ExternalIP *ExternalIPAddressResponse
|
||||
}
|
||||
|
||||
type ExternalIPAddressResponse struct {
|
||||
XMLName xml.Name `xml:"GetExternalIPAddressResponse"`
|
||||
IPAddress string `xml:"NewExternalIPAddress"`
|
||||
}
|
||||
|
||||
type ExternalIPAddress struct {
|
||||
XMLName xml.Name `xml:"NewExternalIPAddress"`
|
||||
IP string
|
||||
}
|
||||
|
||||
type UPNPService struct {
|
||||
ServiceType string `xml:"serviceType"`
|
||||
ControlURL string `xml:"controlURL"`
|
||||
}
|
||||
|
||||
type DeviceList struct {
|
||||
Device []Device `xml:"device"`
|
||||
}
|
||||
|
||||
type ServiceList struct {
|
||||
Service []UPNPService `xml:"service"`
|
||||
}
|
||||
|
||||
type Device struct {
|
||||
XMLName xml.Name `xml:"device"`
|
||||
DeviceType string `xml:"deviceType"`
|
||||
DeviceList DeviceList `xml:"deviceList"`
|
||||
ServiceList ServiceList `xml:"serviceList"`
|
||||
}
|
||||
|
||||
type Root struct {
|
||||
Device Device
|
||||
}
|
||||
|
||||
func getChildDevice(d *Device, deviceType string) *Device {
|
||||
dl := d.DeviceList.Device
|
||||
for i := 0; i < len(dl); i++ {
|
||||
if strings.Index(dl[i].DeviceType, deviceType) >= 0 {
|
||||
return &dl[i]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getChildService(d *Device, serviceType string) *UPNPService {
|
||||
sl := d.ServiceList.Service
|
||||
for i := 0; i < len(sl); i++ {
|
||||
if strings.Index(sl[i].ServiceType, serviceType) >= 0 {
|
||||
return &sl[i]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func localIPv4() (net.IP, error) {
|
||||
tt, err := net.Interfaces()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, t := range tt {
|
||||
aa, err := t.Addrs()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, a := range aa {
|
||||
ipnet, ok := a.(*net.IPNet)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
v4 := ipnet.IP.To4()
|
||||
if v4 == nil || v4[0] == 127 { // loopback address
|
||||
continue
|
||||
}
|
||||
return v4, nil
|
||||
}
|
||||
}
|
||||
return nil, errors.New("cannot find local IP address")
|
||||
}
|
||||
|
||||
func getServiceURL(rootURL string) (url, urnDomain string, err error) {
|
||||
r, err := http.Get(rootURL)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer r.Body.Close()
|
||||
if r.StatusCode >= 400 {
|
||||
err = errors.New(string(r.StatusCode))
|
||||
return
|
||||
}
|
||||
var root Root
|
||||
err = xml.NewDecoder(r.Body).Decode(&root)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
a := &root.Device
|
||||
if strings.Index(a.DeviceType, "InternetGatewayDevice:1") < 0 {
|
||||
err = errors.New("No InternetGatewayDevice")
|
||||
return
|
||||
}
|
||||
b := getChildDevice(a, "WANDevice:1")
|
||||
if b == nil {
|
||||
err = errors.New("No WANDevice")
|
||||
return
|
||||
}
|
||||
c := getChildDevice(b, "WANConnectionDevice:1")
|
||||
if c == nil {
|
||||
err = errors.New("No WANConnectionDevice")
|
||||
return
|
||||
}
|
||||
d := getChildService(c, "WANIPConnection:1")
|
||||
if d == nil {
|
||||
// Some routers don't follow the UPnP spec, and put WanIPConnection under WanDevice,
|
||||
// instead of under WanConnectionDevice
|
||||
d = getChildService(b, "WANIPConnection:1")
|
||||
|
||||
if d == nil {
|
||||
err = errors.New("No WANIPConnection")
|
||||
return
|
||||
}
|
||||
}
|
||||
// Extract the domain name, which isn't always 'schemas-upnp-org'
|
||||
urnDomain = strings.Split(d.ServiceType, ":")[1]
|
||||
url = combineURL(rootURL, d.ControlURL)
|
||||
return
|
||||
}
|
||||
|
||||
func combineURL(rootURL, subURL string) string {
|
||||
protocolEnd := "://"
|
||||
protoEndIndex := strings.Index(rootURL, protocolEnd)
|
||||
a := rootURL[protoEndIndex+len(protocolEnd):]
|
||||
rootIndex := strings.Index(a, "/")
|
||||
return rootURL[0:protoEndIndex+len(protocolEnd)+rootIndex] + subURL
|
||||
}
|
||||
|
||||
func soapRequest(url, function, message, domain string) (r *http.Response, err error) {
|
||||
fullMessage := "<?xml version=\"1.0\" ?>" +
|
||||
"<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\r\n" +
|
||||
"<s:Body>" + message + "</s:Body></s:Envelope>"
|
||||
|
||||
req, err := http.NewRequest("POST", url, strings.NewReader(fullMessage))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Content-Type", "text/xml ; charset=\"utf-8\"")
|
||||
req.Header.Set("User-Agent", "Darwin/10.0.0, UPnP/1.0, MiniUPnPc/1.3")
|
||||
//req.Header.Set("Transfer-Encoding", "chunked")
|
||||
req.Header.Set("SOAPAction", "\"urn:"+domain+":service:WANIPConnection:1#"+function+"\"")
|
||||
req.Header.Set("Connection", "Close")
|
||||
req.Header.Set("Cache-Control", "no-cache")
|
||||
req.Header.Set("Pragma", "no-cache")
|
||||
|
||||
// log.Stderr("soapRequest ", req)
|
||||
|
||||
r, err = http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
/*if r.Body != nil {
|
||||
defer r.Body.Close()
|
||||
}*/
|
||||
|
||||
if r.StatusCode >= 400 {
|
||||
// log.Stderr(function, r.StatusCode)
|
||||
err = errors.New("Error " + strconv.Itoa(r.StatusCode) + " for " + function)
|
||||
r = nil
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type statusInfo struct {
|
||||
externalIpAddress string
|
||||
}
|
||||
|
||||
func (n *upnpNAT) getExternalIPAddress() (info statusInfo, err error) {
|
||||
|
||||
message := "<u:GetExternalIPAddress xmlns:u=\"urn:" + n.urnDomain + ":service:WANIPConnection:1\">\r\n" +
|
||||
"</u:GetExternalIPAddress>"
|
||||
|
||||
var response *http.Response
|
||||
response, err = soapRequest(n.serviceURL, "GetExternalIPAddress", message, n.urnDomain)
|
||||
if response != nil {
|
||||
defer response.Body.Close()
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var envelope Envelope
|
||||
data, err := ioutil.ReadAll(response.Body)
|
||||
reader := bytes.NewReader(data)
|
||||
xml.NewDecoder(reader).Decode(&envelope)
|
||||
|
||||
info = statusInfo{envelope.Soap.ExternalIP.IPAddress}
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (n *upnpNAT) GetExternalAddress() (addr net.IP, err error) {
|
||||
info, err := n.getExternalIPAddress()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
addr = net.ParseIP(info.externalIpAddress)
|
||||
return
|
||||
}
|
||||
|
||||
func (n *upnpNAT) AddPortMapping(protocol string, externalPort, internalPort int, description string, timeout int) (mappedExternalPort int, err error) {
|
||||
// A single concatenation would break ARM compilation.
|
||||
message := "<u:AddPortMapping xmlns:u=\"urn:" + n.urnDomain + ":service:WANIPConnection:1\">\r\n" +
|
||||
"<NewRemoteHost></NewRemoteHost><NewExternalPort>" + strconv.Itoa(externalPort)
|
||||
message += "</NewExternalPort><NewProtocol>" + protocol + "</NewProtocol>"
|
||||
message += "<NewInternalPort>" + strconv.Itoa(internalPort) + "</NewInternalPort>" +
|
||||
"<NewInternalClient>" + n.ourIP + "</NewInternalClient>" +
|
||||
"<NewEnabled>1</NewEnabled><NewPortMappingDescription>"
|
||||
message += description +
|
||||
"</NewPortMappingDescription><NewLeaseDuration>" + strconv.Itoa(timeout) +
|
||||
"</NewLeaseDuration></u:AddPortMapping>"
|
||||
|
||||
var response *http.Response
|
||||
response, err = soapRequest(n.serviceURL, "AddPortMapping", message, n.urnDomain)
|
||||
if response != nil {
|
||||
defer response.Body.Close()
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: check response to see if the port was forwarded
|
||||
// log.Println(message, response)
|
||||
// JAE:
|
||||
// body, err := ioutil.ReadAll(response.Body)
|
||||
// fmt.Println(string(body), err)
|
||||
mappedExternalPort = externalPort
|
||||
_ = response
|
||||
return
|
||||
}
|
||||
|
||||
func (n *upnpNAT) DeletePortMapping(protocol string, externalPort, internalPort int) (err error) {
|
||||
|
||||
message := "<u:DeletePortMapping xmlns:u=\"urn:" + n.urnDomain + ":service:WANIPConnection:1\">\r\n" +
|
||||
"<NewRemoteHost></NewRemoteHost><NewExternalPort>" + strconv.Itoa(externalPort) +
|
||||
"</NewExternalPort><NewProtocol>" + protocol + "</NewProtocol>" +
|
||||
"</u:DeletePortMapping>"
|
||||
|
||||
var response *http.Response
|
||||
response, err = soapRequest(n.serviceURL, "DeletePortMapping", message, n.urnDomain)
|
||||
if response != nil {
|
||||
defer response.Body.Close()
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: check response to see if the port was deleted
|
||||
// log.Println(message, response)
|
||||
_ = response
|
||||
return
|
||||
}
|
||||
-15
@@ -1,15 +0,0 @@
|
||||
package p2p
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
)
|
||||
|
||||
// doubleSha256 calculates sha256(sha256(b)) and returns the resulting bytes.
|
||||
func doubleSha256(b []byte) []byte {
|
||||
hasher := sha256.New()
|
||||
hasher.Write(b)
|
||||
sum := hasher.Sum(nil)
|
||||
hasher.Reset()
|
||||
hasher.Write(sum)
|
||||
return hasher.Sum(nil)
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
package p2p
|
||||
|
||||
const Version = "0.3.0"
|
||||
Reference in New Issue
Block a user