@ -3,7 +3,6 @@ package p2p
import (
"fmt"
"math"
"net"
"sync"
"time"
@ -42,6 +41,10 @@ type AddrBook interface {
Save ( )
}
// PeerFilterFunc to be implemented by filter hooks after a new Peer has been
// fully setup.
type PeerFilterFunc func ( IPeerSet , Peer ) error
//-----------------------------------------------------------------------------
// Switch handles peer connections and exposes an API to receive incoming messages
@ -52,7 +55,6 @@ type Switch struct {
cmn . BaseService
config * config . P2PConfig
listeners [ ] Listener
reactors map [ string ] Reactor
chDescs [ ] * conn . ChannelDescriptor
reactorsByCh map [ byte ] Reactor
@ -63,8 +65,10 @@ type Switch struct {
nodeKey * NodeKey // our node privkey
addrBook AddrBook
filterConnByAddr func ( net . Addr ) error
filterConnByID func ( ID ) error
transport Transport
filterTimeout time . Duration
peerFilters [ ] PeerFilterFunc
mConfig conn . MConnConfig
@ -77,16 +81,22 @@ type Switch struct {
type SwitchOption func ( * Switch )
// NewSwitch creates a new Switch with the given config.
func NewSwitch ( cfg * config . P2PConfig , options ... SwitchOption ) * Switch {
func NewSwitch (
cfg * config . P2PConfig ,
transport Transport ,
options ... SwitchOption ,
) * Switch {
sw := & Switch {
config : cfg ,
reactors : make ( map [ string ] Reactor ) ,
chDescs : make ( [ ] * conn . ChannelDescriptor , 0 ) ,
reactorsByCh : make ( map [ byte ] Reactor ) ,
peers : NewPeerSet ( ) ,
dialing : cmn . NewCMap ( ) ,
reconnecting : cmn . NewCMap ( ) ,
metrics : NopMetrics ( ) ,
config : cfg ,
reactors : make ( map [ string ] Reactor ) ,
chDescs : make ( [ ] * conn . ChannelDescriptor , 0 ) ,
reactorsByCh : make ( map [ byte ] Reactor ) ,
peers : NewPeerSet ( ) ,
dialing : cmn . NewCMap ( ) ,
reconnecting : cmn . NewCMap ( ) ,
metrics : NopMetrics ( ) ,
transport : transport ,
filterTimeout : defaultFilterTimeout ,
}
// Ensure we have a completely undeterministic PRNG.
@ -109,6 +119,16 @@ func NewSwitch(cfg *config.P2PConfig, options ...SwitchOption) *Switch {
return sw
}
// SwitchFilterTimeout sets the timeout used for peer filters.
func SwitchFilterTimeout ( timeout time . Duration ) SwitchOption {
return func ( sw * Switch ) { sw . filterTimeout = timeout }
}
// SwitchPeerFilters sets the filters for rejection of new peers.
func SwitchPeerFilters ( filters ... PeerFilterFunc ) SwitchOption {
return func ( sw * Switch ) { sw . peerFilters = filters }
}
// WithMetrics sets the metrics.
func WithMetrics ( metrics * Metrics ) SwitchOption {
return func ( sw * Switch ) { sw . metrics = metrics }
@ -148,24 +168,6 @@ func (sw *Switch) Reactor(name string) Reactor {
return sw . reactors [ name ]
}
// AddListener adds the given listener to the switch for listening to incoming peer connections.
// NOTE: Not goroutine safe.
func ( sw * Switch ) AddListener ( l Listener ) {
sw . listeners = append ( sw . listeners , l )
}
// Listeners returns the list of listeners the switch listens on.
// NOTE: Not goroutine safe.
func ( sw * Switch ) Listeners ( ) [ ] Listener {
return sw . listeners
}
// IsListening returns true if the switch has at least one listener.
// NOTE: Not goroutine safe.
func ( sw * Switch ) IsListening ( ) bool {
return len ( sw . listeners ) > 0
}
// SetNodeInfo sets the switch's NodeInfo for checking compatibility and handshaking with other nodes.
// NOTE: Not goroutine safe.
func ( sw * Switch ) SetNodeInfo ( nodeInfo NodeInfo ) {
@ -187,7 +189,7 @@ func (sw *Switch) SetNodeKey(nodeKey *NodeKey) {
//---------------------------------------------------------------------
// Service start/stop
// OnStart implements BaseService. It starts all the reactors, peers, and listen ers.
// OnStart implements BaseService. It starts all the reactors and pe ers.
func ( sw * Switch ) OnStart ( ) error {
// Start reactors
for _ , reactor := range sw . reactors {
@ -196,25 +198,21 @@ func (sw *Switch) OnStart() error {
return cmn . ErrorWrap ( err , "failed to start %v" , reactor )
}
}
// Start listeners
for _ , listener := range sw . listeners {
go sw . listenerRoutine ( listener )
}
// Start accepting Peers.
go sw . acceptRoutine ( )
return nil
}
// OnStop implements BaseService. It stops all listeners, peers, and reactors.
// OnStop implements BaseService. It stops all peers and reactors.
func ( sw * Switch ) 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 . Remove ( peer )
for _ , p := range sw . peers . List ( ) {
p . Stop ( )
sw . peers . Remove ( p )
}
// Stop reactors
sw . Logger . Debug ( "Switch: Stopping reactors" )
for _ , reactor := range sw . reactors {
@ -459,42 +457,46 @@ func (sw *Switch) IsDialingOrExistingAddress(addr *NetAddress) bool {
( ! sw . config . AllowDuplicateIP && sw . peers . HasIP ( addr . IP ) )
}
//------------------------------------------------------------------------------------
// Connection filtering
// FilterConnByAddr returns an error if connecting to the given address is forbidden.
func ( sw * Switch ) FilterConnByAddr ( addr net . Addr ) error {
if sw . filterConnByAddr != nil {
return sw . filterConnByAddr ( addr )
}
return nil
}
// FilterConnByID returns an error if connecting to the given peer ID is forbidden.
func ( sw * Switch ) FilterConnByID ( id ID ) error {
if sw . filterConnByID != nil {
return sw . filterConnByID ( id )
}
return nil
}
// SetAddrFilter sets the function for filtering connections by address.
func ( sw * Switch ) SetAddrFilter ( f func ( net . Addr ) error ) {
sw . filterConnByAddr = f
}
// SetIDFilter sets the function for filtering connections by peer ID.
func ( sw * Switch ) SetIDFilter ( f func ( ID ) error ) {
sw . filterConnByID = f
}
func ( sw * Switch ) acceptRoutine ( ) {
for {
p , err := sw . transport . Accept ( peerConfig {
chDescs : sw . chDescs ,
onPeerError : sw . StopPeerForError ,
reactorsByCh : sw . reactorsByCh ,
} )
if err != nil {
switch err . ( type ) {
case ErrRejected :
rErr := err . ( ErrRejected )
if rErr . IsSelf ( ) {
// Remove the given address from the address book and add to our addresses
// to avoid dialing in the future.
addr := rErr . Addr ( )
sw . addrBook . RemoveAddress ( & addr )
sw . addrBook . AddOurAddress ( & addr )
}
//------------------------------------------------------------------------------------
sw . Logger . Info (
"Inbound Peer rejected" ,
"err" , err ,
"numPeers" , sw . peers . Size ( ) ,
)
continue
case * ErrTransportClosed :
sw . Logger . Error (
"Stopped accept routine, as transport is closed" ,
"numPeers" , sw . peers . Size ( ) ,
)
default :
sw . Logger . Error (
"Accept on transport errored" ,
"err" , err ,
"numPeers" , sw . peers . Size ( ) ,
)
}
func ( sw * Switch ) listenerRoutine ( l Listener ) {
for {
inConn , ok := <- l . Connections ( )
if ! ok {
break
}
@ -503,41 +505,25 @@ func (sw *Switch) listenerRoutine(l Listener) {
if in >= sw . config . MaxNumInboundPeers {
sw . Logger . Info (
"Ignoring inbound connection: already have enough inbound peers" ,
"address" , inConn . RemoteAddr ( ) . String ( ) ,
"address" , p . NodeInfo ( ) . NetAddress ( ) . String ( ) ,
"have" , in ,
"max" , sw . config . MaxNumInboundPeers ,
)
inConn . Close ( )
continue
}
// New inbound connection!
err := sw . addInboundPeerWithConfig ( inConn , sw . config )
if err != nil {
sw . Logger . Info ( "Ignoring inbound connection: error while adding peer" , "address" , inConn . RemoteAddr ( ) . String ( ) , "err" , err )
_ = p . Stop ( )
continue
}
}
// cleanup
}
// closes conn if err is returned
func ( sw * Switch ) addInboundPeerWithConfig (
conn net . Conn ,
config * config . P2PConfig ,
) error {
peerConn , err := newInboundPeerConn ( conn , config , sw . nodeKey . PrivKey )
if err != nil {
conn . Close ( ) // peer is nil
return err
}
if err = sw . addPeer ( peerConn ) ; err != nil {
peerConn . CloseConn ( )
return err
if err := sw . addPeer ( p ) ; err != nil {
_ = p . Stop ( )
sw . Logger . Info (
"Ignoring inbound connection: error while adding peer" ,
"err" , err ,
"id" , p . ID ( ) ,
)
}
}
return nil
}
// dial the peer; make secret connection; authenticate against the dialed ID;
@ -547,109 +533,88 @@ func (sw *Switch) addInboundPeerWithConfig(
// StopPeerForError is called
func ( sw * Switch ) addOutboundPeerWithConfig (
addr * NetAddress ,
con fi g * config . P2PConfig ,
cfg * config . P2PConfig ,
persistent bool ,
) error {
sw . Logger . Info ( "Dialing peer" , "address" , addr )
peerConn , err := newOutboundPeerConn (
addr ,
config ,
persistent ,
sw . nodeKey . PrivKey ,
)
// XXX(xla): Remove the leakage of test concerns in implementation.
if cfg . TestDialFail {
go sw . reconnectToPeer ( addr )
return fmt . Errorf ( "dial err (peerConfig.DialFail == true)" )
}
p , err := sw . transport . Dial ( * addr , peerConfig {
chDescs : sw . chDescs ,
onPeerError : sw . StopPeerForError ,
persistent : persistent ,
reactorsByCh : sw . reactorsByCh ,
} )
if err != nil {
switch e := err . ( type ) {
case ErrRejected :
if e . IsSelf ( ) {
// Remove the given address from the address book and add to our addresses
// to avoid dialing in the future.
sw . addrBook . RemoveAddress ( addr )
sw . addrBook . AddOurAddress ( addr )
}
}
if persistent {
go sw . reconnectToPeer ( addr )
}
return err
}
if err := sw . addPeer ( peerConn ) ; err != nil {
peerConn . CloseConn ( )
if err := sw . addPeer ( p ) ; err != nil {
_ = p . Stop ( )
return err
}
return nil
}
// addPeer performs the Tendermint P2P handshake with a peer
// that already has a SecretConnection. If all goes well,
// it starts the peer and adds it to the switch.
// NOTE: This performs a blocking handshake before the peer is added.
// NOTE: If error is returned, caller is responsible for calling
// peer.CloseConn()
func ( sw * Switch ) addPeer ( pc peerConn ) error {
addr := pc . conn . RemoteAddr ( )
if err := sw . FilterConnByAddr ( addr ) ; err != nil {
return err
}
// Exchange NodeInfo on the conn
peerNodeInfo , err := pc . HandshakeTimeout ( sw . nodeInfo , time . Duration ( sw . config . HandshakeTimeout ) )
if err != nil {
return err
func ( sw * Switch ) filterPeer ( p Peer ) error {
// Avoid duplicate
if sw . peers . Has ( p . ID ( ) ) {
return ErrRejected { id : p . ID ( ) , isDuplicate : true }
}
peerID := peerNodeInfo . ID
errc := make ( chan error , len ( sw . peerFilters ) )
// ensure connection key matches self reported key
connID := pc . ID ( )
if peerID != connID {
return fmt . Errorf (
"nodeInfo.ID() (%v) doesn't match conn.ID() (%v)" ,
peerID ,
connID ,
)
for _ , f := range sw . peerFilters {
go func ( f PeerFilterFunc , p Peer , errc chan <- error ) {
errc <- f ( sw . peers , p )
} ( f , p , errc )
}
// Validate the peers nodeInfo
if err := peerNodeInfo . Validate ( ) ; err != nil {
return err
}
// Avoid self
if sw . nodeKey . ID ( ) == peerID {
addr := peerNodeInfo . NetAddress ( )
// remove the given address from the address book
// and add to our addresses to avoid dialing again
if sw . addrBook != nil {
sw . addrBook . RemoveAddress ( addr )
sw . addrBook . AddOurAddress ( addr )
for i := 0 ; i < cap ( errc ) ; i ++ {
select {
case err := <- errc :
if err != nil {
return ErrRejected { id : p . ID ( ) , err : err , isFiltered : true }
}
case <- time . After ( sw . filterTimeout ) :
return ErrFilterTimeout { }
}
return ErrSwitchConnectToSelf { addr }
}
// Avoid duplicate
if sw . peers . Has ( peerID ) {
return ErrSwitchDuplicatePeerID { peerID }
}
// Check for duplicate connection or peer info IP.
if ! sw . config . AllowDuplicateIP &&
( sw . peers . HasIP ( pc . RemoteIP ( ) ) ||
sw . peers . HasIP ( peerNodeInfo . NetAddress ( ) . IP ) ) {
return ErrSwitchDuplicatePeerIP { pc . RemoteIP ( ) }
}
// Filter peer against ID white list
if err := sw . FilterConnByID ( peerID ) ; err != nil {
return err
}
return nil
}
// Check version, chain id
if err := sw . nodeInfo . CompatibleWith ( peerNodeInfo ) ; err != nil {
// addPeer starts up the Peer and adds it to the Switch.
func ( sw * Switch ) addPeer ( p Peer ) error {
if err := sw . filterPeer ( p ) ; err != nil {
return err
}
peer := newPeer ( pc , sw . mConfig , peerNodeInfo , sw . reactorsByCh , sw . chDescs , sw . StopPeerForError )
peer . SetLogger ( sw . Logger . With ( "peer" , addr ) )
peer . Logger . Info ( "Successful handshake with peer" , "peerNodeInfo" , peerNodeInfo )
p . SetLogger ( sw . Logger . With ( "peer" , p . NodeInfo ( ) . NetAddress ( ) . String ) )
// All good. Start peer
if sw . IsRunning ( ) {
if err = sw . startInitPeer ( peer ) ; err != nil {
if err : = sw . startInitPeer ( p ) ; err != nil {
return err
}
}
@ -657,25 +622,30 @@ func (sw *Switch) addPeer(pc peerConn) error {
// Add the peer to .peers.
// We start it first so that a peer in the list is safe to Stop.
// It should not err since we already checked peers.Has().
if err := sw . peers . Add ( peer ) ; err != nil {
if err := sw . peers . Add ( p ) ; err != nil {
return err
}
sw . Logger . Info ( "Added peer" , "peer" , p )
sw . metrics . Peers . Add ( float64 ( 1 ) )
sw . Logger . Info ( "Added peer" , "peer" , peer )
return nil
}
func ( sw * Switch ) startInitPeer ( peer * p eer ) error {
err := peer . Start ( ) // spawn send/recv routines
func ( sw * Switch ) startInitPeer ( p P eer ) error {
err := p . Start ( ) // spawn send/recv routines
if err != nil {
// Should never happen
sw . Logger . Error ( "Error starting peer" , "peer" , peer , "err" , err )
sw . Logger . Error (
"Error starting peer" ,
"err" , err ,
"peer" , p ,
)
return err
}
for _ , reactor := range sw . reactors {
reactor . AddPeer ( peer )
reactor . AddPeer ( p )
}
return nil