@ -9,133 +9,71 @@ import (
"sync"
"time"
"golang.org/x/net/netutil"
"github.com/tendermint/tendermint/crypto"
"github.com/tendermint/tendermint/libs/log"
"github.com/tendermint/tendermint/libs/protoio"
"github.com/tendermint/tendermint/p2p/conn"
p2pproto "github.com/tendermint/tendermint/proto/tendermint/p2p"
"golang.org/x/net/netutil"
)
const (
defaultDialTimeout = time . Second
defaultFilterTimeout = 5 * time . Second
defaultHandshakeTimeout = 3 * time . Second
MConnProtocol Protocol = "mconn"
TCPProtocol Protocol = "tcp"
)
// MConnProtocol is the MConn protocol identifier.
const MConnProtocol Protocol = "mconn"
// MConnTransportOption sets an option for MConnTransport.
type MConnTransportOption func ( * MConnTransport )
// MConnTransportMaxIncomingConnections sets the maximum number of
// simultaneous incoming connections. Default: 0 (unlimited)
func MConnTransportMaxIncomingConnections ( max int ) MConnTransportOption {
return func ( mt * MConnTransport ) { mt . maxIncomingConnections = max }
}
// MConnTransportFilterTimeout sets the timeout for filter callbacks.
func MConnTransportFilterTimeout ( timeout time . Duration ) MConnTransportOption {
return func ( mt * MConnTransport ) { mt . filterTimeout = timeout }
}
// MConnTransportConnFilters sets connection filters.
func MConnTransportConnFilters ( filters ... ConnFilterFunc ) MConnTransportOption {
return func ( mt * MConnTransport ) { mt . connFilters = filters }
}
// ConnFilterFunc is a callback for connection filtering. If it returns an
// error, the connection is rejected. The set of existing connections is passed
// along with the new connection and all resolved IPs.
type ConnFilterFunc func ( ConnSet , net . Conn , [ ] net . IP ) error
// ConnDuplicateIPFilter resolves and keeps all ips for an incoming connection
// and refuses new ones if they come from a known ip.
var ConnDuplicateIPFilter ConnFilterFunc = func ( cs ConnSet , c net . Conn , ips [ ] net . IP ) error {
for _ , ip := range ips {
if cs . HasIP ( ip ) {
return ErrRejected {
conn : c ,
err : fmt . Errorf ( "ip<%v> already connected" , ip ) ,
isDuplicate : true ,
}
}
}
return nil
// MConnTransportOptions sets options for MConnTransport.
type MConnTransportOptions struct {
// MaxAcceptedConnections is the maximum number of simultaneous accepted
// (incoming) connections. Beyond this, new connections will block until
// a slot is free. 0 means unlimited.
//
// FIXME: We may want to replace this with connection accounting in the
// Router, since it will need to do e.g. rate limiting and such as well.
// But it might also make sense to have per-transport limits.
MaxAcceptedConnections uint32
}
// MConnTransport is a Transport implementation using the current multiplexed
// Tendermint protocol ("MConn"). It inherits lots of code and logic from the
// previous implementation for parity with the current P2P stack (such as
// connection filtering, peer verification, and panic handling), which should be
// moved out of the transport once the rest of the P2P stack is rewritten.
// Tendermint protocol ("MConn").
type MConnTransport struct {
privKey crypto . PrivKey
nodeInfo NodeInfo
channelDescs [ ] * ChannelDescriptor
logger log . Logger
options MConnTransportOptions
mConnConfig conn . MConnConfig
channelDescs [ ] * ChannelDescriptor
closeCh chan struct { }
closeOnce sync . Once
maxIncomingConnections int
dialTimeout time . Duration
handshakeTimeout time . Duration
filterTimeout time . Duration
logger log . Logger
listener net . Listener
closeOnce sync . Once
chAccept chan * mConnConnection
chError chan error
chClose chan struct { }
// FIXME: This is a vestige from the old transport, and should be managed
// by the router once we rewrite the P2P core.
conns ConnSet
connFilters [ ] ConnFilterFunc
}
// NewMConnTransport sets up a new MConn transport.
// NewMConnTransport sets up a new MConnection transport. This uses the
// proprietary Tendermint MConnection protocol, which is implemented as
// conn.MConnection.
func NewMConnTransport (
logger log . Logger ,
nodeInfo NodeInfo ,
privKey crypto . PrivKey ,
mConnConfig conn . MConnConfig ,
opts ... MConnTransportOption ,
channelDescs [ ] * ChannelDescriptor ,
options MConnTransportOptions ,
) * MConnTransport {
m := & MConnTransport {
privKey : privKey ,
nodeInfo : nodeInfo ,
return & MConnTransport {
logger : logger ,
options : options ,
mConnConfig : mConnConfig ,
channelDescs : [ ] * ChannelDescriptor { } ,
dialTimeout : defaultDialTimeout ,
handshakeTimeout : defaultHandshakeTimeout ,
filterTimeout : defaultFilterTimeout ,
logger : logger ,
chAccept : make ( chan * mConnConnection ) ,
chError : make ( chan error ) ,
chClose : make ( chan struct { } ) ,
conns : NewConnSet ( ) ,
connFilters : [ ] ConnFilterFunc { } ,
}
for _ , opt := range opts {
opt ( m )
closeCh : make ( chan struct { } ) ,
channelDescs : channelDescs ,
}
return m
}
// SetChannelDescriptors implements Transport.
//
// This is not concurrency-safe, and must be called before listening.
//
// FIXME: This is here for compatibility with existing switch code,
// it should be passed via the constructor instead .
func ( m * MConnTransport ) SetChannelDescriptors ( chDescs [ ] * conn . ChannelDescriptor ) {
m . channelDescs = chDescs
// String implements Transport.
func ( m * MConnTransport ) String ( ) string {
return string ( MConnProtocol )
}
// Protocols implements Transport. We support tcp for backwards-compatibility.
func ( m * MConnTransport ) Protocols ( ) [ ] Protocol {
return [ ] Protocol { MConnProtocol , TCPProtocol }
}
// Listen asynchronously listens for inbound connections on the given endpoint.
@ -143,9 +81,9 @@ func (m *MConnTransport) SetChannelDescriptors(chDescs []*conn.ChannelDescriptor
// call Close() to shut down the listener.
func ( m * MConnTransport ) Listen ( endpoint Endpoint ) error {
if m . listener != nil {
return errors . New ( "MConn transport is already listening" )
return errors . New ( "transport is already listening" )
}
err := m . normalizeEndpoint ( & endpoint )
endpoint , e rr := m . normalizeEndpoint ( endpoint )
if err != nil {
return fmt . Errorf ( "invalid MConn listen endpoint %q: %w" , endpoint , err )
}
@ -154,104 +92,50 @@ func (m *MConnTransport) Listen(endpoint Endpoint) error {
if err != nil {
return err
}
if m . maxIncoming Connections > 0 {
m . listener = netutil . LimitListener ( m . listener , m . maxIncomingConnections )
if m . options . MaxAccepted Connections > 0 {
m . listener = netutil . LimitListener ( m . listener , int ( m . options . MaxAcceptedConnections ) )
}
// Spawn a goroutine to accept inbound connections asynchronously.
go m . accept ( )
return nil
}
// accept accepts inbound connections in a loop, and asynchronously handshakes
// with the peer to avoid head-of-line blocking. Established connections are
// passed to Accept() via chAccept.
// See: https://github.com/tendermint/tendermint/issues/204
func ( m * MConnTransport ) accept ( ) {
for {
tcpConn , err := m . listener . Accept ( )
if err != nil {
// We have to check for closure first, since we don't want to
// propagate "use of closed network connection" errors.
select {
case <- m . chClose :
default :
// We also select on chClose here, in case the transport closes
// while we're blocked on error propagation.
select {
case m . chError <- err :
case <- m . chClose :
}
}
return
}
go func ( ) {
err := m . filterTCPConn ( tcpConn )
if err != nil {
if err := tcpConn . Close ( ) ; err != nil {
m . logger . Debug ( "failed to close TCP connection" , "err" , err )
}
select {
case m . chError <- err :
case <- m . chClose :
}
return
}
// Accept implements Transport.
func ( m * MConnTransport ) Accept ( ctx context . Context ) ( Connection , error ) {
if m . listener == nil {
return nil , errors . New ( "transport is not listening" )
}
conn , err := newMConnConnection ( m , tcpConn , "" )
if err != nil {
m . conns . Remove ( tcpConn )
if err := tcpConn . Close ( ) ; err != nil {
m . logger . Debug ( "failed to close TCP connection" , "err" , err )
}
select {
case m . chError <- err :
case <- m . chClose :
}
} else {
select {
case m . chAccept <- conn :
case <- m . chClose :
if err := tcpConn . Close ( ) ; err != nil {
m . logger . Debug ( "failed to close TCP connection" , "err" , err )
}
}
if deadline , ok := ctx . Deadline ( ) ; ok {
if tcpListener , ok := m . listener . ( * net . TCPListener ) ; ok {
// FIXME: This probably needs to have a goroutine that overrides the
// deadline on context cancellation as well.
if err := tcpListener . SetDeadline ( deadline ) ; err != nil {
return nil , err
}
} ( )
}
}
}
// Accept implements Transport.
//
// accept() runs a concurrent accept loop that accepts inbound connections
// and then handshakes in a non-blocking fashion. The handshaked and validated
// connections are returned via this call, picking them off of the chAccept
// channel (or the handshake error, if any).
func ( m * MConnTransport ) Accept ( ctx context . Context ) ( Connection , error ) {
select {
case conn := <- m . chAccept :
return conn , nil
case err := <- m . chError :
return nil , err
case <- m . chClose :
return nil , ErrTransportClosed { }
case <- ctx . Done ( ) :
return nil , ctx . Err ( )
tcpConn , err := m . listener . Accept ( )
if err != nil {
select {
case <- m . closeCh :
return nil , io . EOF
case <- ctx . Done ( ) :
return nil , ctx . Err ( )
default :
return nil , err
}
}
return newMConnConnection ( m . logger , tcpConn , m . mConnConfig , m . channelDescs ) , nil
}
// Dial implements Transport.
func ( m * MConnTransport ) Dial ( ctx context . Context , endpoint Endpoint ) ( Connection , error ) {
err := m . normalizeEndpoint ( & endpoint )
endpoint , err := m . normalizeEndpoint ( endpoint )
if err != nil {
return nil , err
}
ctx , cancel := context . WithTimeout ( ctx , m . dialTimeout )
defer cancel ( )
dialer := net . Dialer { }
tcpConn , err := dialer . DialContext ( ctx , "tcp" ,
net . JoinHostPort ( endpoint . IP . String ( ) , fmt . Sprintf ( "%v" , endpoint . Port ) ) )
@ -259,24 +143,7 @@ func (m *MConnTransport) Dial(ctx context.Context, endpoint Endpoint) (Connectio
return nil , err
}
err = m . filterTCPConn ( tcpConn )
if err != nil {
if err := tcpConn . Close ( ) ; err != nil {
m . logger . Debug ( "failed to close TCP connection" , "err" , err )
}
return nil , err
}
conn , err := newMConnConnection ( m , tcpConn , endpoint . PeerID )
if err != nil {
m . conns . Remove ( tcpConn )
if err := tcpConn . Close ( ) ; err != nil {
m . logger . Debug ( "failed to close TCP connection" , "err" , err )
}
return nil , err
}
return conn , nil
return newMConnConnection ( m . logger , tcpConn , m . mConnConfig , m . channelDescs ) , nil
}
// Endpoints implements Transport.
@ -284,22 +151,21 @@ func (m *MConnTransport) Endpoints() []Endpoint {
if m . listener == nil {
return [ ] Endpoint { }
}
addr := m . listener . Addr ( ) . ( * net . TCPAddr )
return [ ] Endpoint { {
endpoint := Endpoint {
Protocol : MConnProtocol ,
PeerID : m . nodeInfo . ID ( ) ,
IP : addr . IP ,
Port : uint16 ( addr . Port ) ,
} }
}
if addr , ok := m . listener . Addr ( ) . ( * net . TCPAddr ) ; ok {
endpoint . IP = addr . IP
endpoint . Port = uint16 ( addr . Port )
}
return [ ] Endpoint { endpoint }
}
// Close implements Transport.
func ( m * MConnTransport ) Close ( ) error {
var err error
m . closeOnce . Do ( func ( ) {
// We have to close chClose first, so that accept() will detect
// the closure and not propagate the error.
close ( m . chClose )
close ( m . closeCh ) // must be closed first, to handle error in Accept()
if m . listener != nil {
err = m . listener . Close ( )
}
@ -307,88 +173,38 @@ func (m *MConnTransport) Close() error {
return err
}
// filterTCPConn filters a TCP connection, rejecting it if this function errors.
func ( m * MConnTransport ) filterTCPConn ( tcpConn net . Conn ) error {
if m . conns . Has ( tcpConn ) {
return ErrRejected { conn : tcpConn , isDuplicate : true }
}
host , _ , err := net . SplitHostPort ( tcpConn . RemoteAddr ( ) . String ( ) )
if err != nil {
return err
}
ip := net . ParseIP ( host )
if ip == nil {
return fmt . Errorf ( "connection address has invalid IP address %q" , host )
}
// Apply filter callbacks.
chErr := make ( chan error , len ( m . connFilters ) )
for _ , connFilter := range m . connFilters {
go func ( connFilter ConnFilterFunc ) {
chErr <- connFilter ( m . conns , tcpConn , [ ] net . IP { ip } )
} ( connFilter )
}
for i := 0 ; i < cap ( chErr ) ; i ++ {
select {
case err := <- chErr :
if err != nil {
return ErrRejected { conn : tcpConn , err : err , isFiltered : true }
}
case <- time . After ( m . filterTimeout ) :
return ErrFilterTimeout { }
}
}
// FIXME: Doesn't really make sense to set this here, but we preserve the
// behavior from the previous P2P transport implementation. This should
// be moved to the router.
m . conns . Set ( tcpConn , [ ] net . IP { ip } )
return nil
}
// normalizeEndpoint normalizes and validates an endpoint.
func ( m * MConnTransport ) normalizeEndpoint ( endpoint * Endpoint ) error {
if endpoint == nil {
return errors . New ( "nil endpoint" )
}
func ( m * MConnTransport ) normalizeEndpoint ( endpoint Endpoint ) ( Endpoint , error ) {
if err := endpoint . Validate ( ) ; err != nil {
return err
return Endpoint { } , err
}
if endpoint . Protocol == "" {
endpoint . Protocol = MConnProtocol
}
if endpoint . Protocol != MConnProtocol {
return fmt . Errorf ( "unsupported protocol %q" , endpoint . Protocol )
if endpoint . Protocol != MConnProtocol && endpoint . Protocol != TCPProtocol {
return Endpoint { } , fmt . Errorf ( "unsupported protocol %q" , endpoint . Protocol )
}
if len ( endpoint . IP ) == 0 {
return errors . New ( "endpoint must have an IP address" )
return Endpoint { } , errors . New ( "endpoint must have an IP address" )
}
if endpoint . Path != "" {
return fmt . Errorf ( "endpoint cannot have path (got %q)" , endpoint . Path )
return Endpoint { } , fmt . Errorf ( "endpoint cannot have path (got %q)" , endpoint . Path )
}
if endpoint . Port == 0 {
endpoint . Port = 26657
}
return nil
return endpoint , nil
}
// mConnConnection implements Connection for MConnTransport. It takes a base TCP
// connection and upgrades it to MConnection over an encrypted SecretConnection.
// mConnConnection implements Connection for MConnTransport.
type mConnConnection struct {
logger log . Logger
transport * MConnTransport
secretConn * conn . SecretConnection
mConn * conn . MConnection
peerInfo NodeInfo
logger log . Logger
conn net . Conn
mConnConfig conn . MConnConfig
channelDescs [ ] * ChannelDescriptor
receiveCh chan mConnMessage
errorCh chan error
closeCh chan struct { }
closeOnce sync . Once
closeOnce sync . Once
chReceive chan mConnMessage
chError chan error
chClose chan struct { }
mconn * conn . MConnection // set during Handshake()
}
// mConnMessage passes MConnection messages through internal channels.
@ -397,184 +213,133 @@ type mConnMessage struct {
payload [ ] byte
}
// newMConnConnection creates a new mConnConnection by handshaking
// with a peer.
// newMConnConnection creates a new mConnConnection.
func newMConnConnection (
transport * MConnTransport ,
tcpConn net . Conn ,
expectPeerID NodeID ,
) ( c * mConnConnection , err error ) {
// FIXME: Since the MConnection code panics, we need to recover here
// and turn it into an error. Be careful not to alias err, so we can
// update it from within this function. We should remove panics instead.
logger log . Logger ,
conn net . Conn ,
mConnConfig conn . MConnConfig ,
channelDescs [ ] * ChannelDescriptor ,
) * mConnConnection {
return & mConnConnection {
logger : logger ,
conn : conn ,
mConnConfig : mConnConfig ,
channelDescs : channelDescs ,
receiveCh : make ( chan mConnMessage ) ,
errorCh : make ( chan error ) ,
closeCh : make ( chan struct { } ) ,
}
}
// Handshake implements Connection.
//
// FIXME: Since the MConnection code panics, we need to recover it and turn it
// into an error. We should remove panics instead.
func ( c * mConnConnection ) Handshake (
ctx context . Context ,
nodeInfo NodeInfo ,
privKey crypto . PrivKey ,
) ( peerInfo NodeInfo , peerKey crypto . PubKey , err error ) {
defer func ( ) {
if r := recover ( ) ; r != nil {
err = ErrRejected {
conn : tcpConn ,
err : fmt . Errorf ( "recovered from panic: %v" , r ) ,
isAuthFailure : true ,
}
err = fmt . Errorf ( "recovered from panic: %v" , r )
}
} ( )
err = tcpConn . SetDeadline ( time . Now ( ) . Add ( transport . handshakeTimeout ) )
if err != nil {
err = ErrRejected {
conn : tcpConn ,
err : fmt . Errorf ( "secret conn failed: %v" , err ) ,
isAuthFailure : true ,
}
return
}
peerInfo , peerKey , err = c . handshake ( ctx , nodeInfo , privKey )
return
}
c = & mConnConnection {
transport : transport ,
chReceive : make ( chan mConnMessage ) ,
chError : make ( chan error ) ,
chClose : make ( chan struct { } ) ,
}
c . secretConn , err = conn . MakeSecretConnection ( tcpConn , transport . privKey )
if err != nil {
err = ErrRejected {
conn : tcpConn ,
err : fmt . Errorf ( "secret conn failed: %v" , err ) ,
isAuthFailure : true ,
}
return
// handshake is a helper for Handshake, simplifying error handling so we can
// keep panic recovery in Handshake. It sets c.mconn.
//
// FIXME: Move this into Handshake() when MConnection no longer panics.
func ( c * mConnConnection ) handshake (
ctx context . Context ,
nodeInfo NodeInfo ,
privKey crypto . PrivKey ,
) ( NodeInfo , crypto . PubKey , error ) {
if c . mconn != nil {
return NodeInfo { } , nil , errors . New ( "connection is already handshaked" )
}
c . peerInfo , err = c . handshake ( )
if err != nil {
err = ErrRejected {
conn : tcpConn ,
err : fmt . Errorf ( "handshake failed: %v" , err ) ,
isAuthFailure : true ,
if deadline , ok := ctx . Deadline ( ) ; ok {
if err := c . conn . SetDeadline ( deadline ) ; err != nil {
return NodeInfo { } , nil , err
}
return
}
// Validate node info.
// FIXME: All of the ID verification code below should be moved to the
// router once implemented.
err = c . peerInfo . Validate ( )
secretConn , err := conn . MakeSecretConnection ( c . conn , privKey )
if err != nil {
err = ErrRejected {
conn : tcpConn ,
err : err ,
isNodeInfoInvalid : true ,
}
return
}
// For outgoing conns, ensure connection key matches dialed key.
if expectPeerID != "" {
peerID := NodeIDFromPubKey ( c . PubKey ( ) )
if expectPeerID != peerID {
err = ErrRejected {
conn : tcpConn ,
id : peerID ,
err : fmt . Errorf (
"conn.ID (%v) dialed ID (%v) mismatch" ,
peerID ,
expectPeerID ,
) ,
isAuthFailure : true ,
}
return
}
return NodeInfo { } , nil , err
}
// Reject self.
if transport . nodeInfo . ID ( ) == c . peerInfo . ID ( ) {
err = ErrRejected {
addr : * NewNetAddress ( c . peerInfo . ID ( ) , c . secretConn . RemoteAddr ( ) ) ,
conn : tcpConn ,
id : c . peerInfo . ID ( ) ,
isSelf : true ,
var pbPeerInfo p2pproto . NodeInfo
errCh := make ( chan error , 2 )
go func ( ) {
_ , err := protoio . NewDelimitedWriter ( secretConn ) . WriteMsg ( nodeInfo . ToProto ( ) )
errCh <- err
} ( )
go func ( ) {
_ , err := protoio . NewDelimitedReader ( secretConn , MaxNodeInfoSize ( ) ) . ReadMsg ( & pbPeerInfo )
errCh <- err
} ( )
for i := 0 ; i < cap ( errCh ) ; i ++ {
if err = <- errCh ; err != nil {
return NodeInfo { } , nil , err
}
return
}
err = transport . nodeInfo . CompatibleWith ( c . peerInfo )
peerInfo , err := NodeInfoFromProto ( & pbPeerInfo )
if err != nil {
err = ErrRejected {
conn : tcpConn ,
err : err ,
id : c . peerInfo . ID ( ) ,
isIncompatible : true ,
}
return
return NodeInfo { } , nil , err
}
err = tcpConn . SetDeadline ( time . Time { } )
if err != nil {
err = ErrRejected {
conn : tcpConn ,
err : fmt . Errorf ( "secret conn failed: %v" , err ) ,
isAuthFailure : true ,
}
return
if err = c . conn . SetDeadline ( time . Time { } ) ; err != nil {
return NodeInfo { } , nil , err
}
// Set up the MConnection wrapper
c . mConn = conn . NewMConnectionWithConfig (
c . secretConn ,
transport . channelDescs ,
c . logger = c . logger . With ( "peer" , c . RemoteEndpoint ( ) . PeerAddress ( peerInfo . NodeID ) )
mconn := conn . NewMConnectionWithConfig (
secretConn ,
c . channelDescs ,
c . onReceive ,
c . onError ,
transport . mConnConfig ,
c . mConnConfig ,
)
// FIXME: Log format is set up for compatibility with existing peer code.
c . logger = transport . logger . With ( "peer" , c . RemoteEndpoint ( ) . NetAddress ( ) )
c . mConn . SetLogger ( c . logger )
err = c . mConn . Start ( )
return c , err
}
// handshake performs an MConn handshake, returning the peer's node info.
func ( c * mConnConnection ) handshake ( ) ( NodeInfo , error ) {
var pbNodeInfo p2pproto . NodeInfo
chErr := make ( chan error , 2 )
go func ( ) {
_ , err := protoio . NewDelimitedWriter ( c . secretConn ) . WriteMsg ( c . transport . nodeInfo . ToProto ( ) )
chErr <- err
} ( )
go func ( ) {
_ , err := protoio . NewDelimitedReader ( c . secretConn , MaxNodeInfoSize ( ) ) . ReadMsg ( & pbNodeInfo )
chErr <- err
} ( )
for i := 0 ; i < cap ( chErr ) ; i ++ {
if err := <- chErr ; err != nil {
return NodeInfo { } , err
}
mconn . SetLogger ( c . logger )
if err = mconn . Start ( ) ; err != nil {
return NodeInfo { } , nil , err
}
c . mconn = mconn
return NodeInfoFromProto ( & pbNodeInfo )
return peerInfo , secretConn . RemotePubKey ( ) , nil
}
// onReceive is a callback for MConnection received messages.
func ( c * mConnConnection ) onReceive ( channelID byte , payload [ ] byte ) {
select {
case c . chReceive <- mConnMessage { channelID : channelID , payload : payload } :
case <- c . chC lose :
case c . receiveCh <- mConnMessage { channelID : channelID , payload : payload } :
case <- c . closeCh :
}
}
// onError is a callback for MConnection errors. The error is passed to
// chError, which is only consumed by ReceiveMessage() for parity with
// the old MConnection behavior.
// onError is a callback for MConnection errors. The error is passed to errorCh,
// which is only consumed by ReceiveMessage() for parity with the old
// MConnection behavior.
func ( c * mConnConnection ) onError ( e interface { } ) {
err , ok := e . ( error )
if ! ok {
err = fmt . Errorf ( "%v" , err )
}
select {
case c . chError <- err :
case <- c . chC lose :
case c . errorCh <- err :
case <- c . closeCh :
}
}
// String displays connection information.
// FIXME: This is here for backwards compatibility with existing code ,
// FIXME: This is here for backwards compatibility with existing logging ,
// it should probably just return RemoteEndpoint().String(), if anything.
func ( c * mConnConnection ) String ( ) string {
endpoint := c . RemoteEndpoint ( )
@ -583,57 +348,44 @@ func (c *mConnConnection) String() string {
// SendMessage implements Connection.
func ( c * mConnConnection ) SendMessage ( channelID byte , msg [ ] byte ) ( bool , error ) {
// We don't check chError here, to preserve old MConnection behavior.
// We don't check errorCh here, to preserve old MConnection behavior.
select {
case <- c . chC lose :
case <- c . closeCh :
return false , io . EOF
default :
return c . mC onn . Send ( channelID , msg ) , nil
return c . mc onn . Send ( channelID , msg ) , nil
}
}
// TrySendMessage implements Connection.
func ( c * mConnConnection ) TrySendMessage ( channelID byte , msg [ ] byte ) ( bool , error ) {
// We don't check chError here, to preserve old MConnection behavior.
// We don't check errorCh here, to preserve old MConnection behavior.
select {
case <- c . chC lose :
case <- c . closeCh :
return false , io . EOF
default :
return c . mC onn . TrySend ( channelID , msg ) , nil
return c . mc onn . TrySend ( channelID , msg ) , nil
}
}
// ReceiveMessage implements Connection.
func ( c * mConnConnection ) ReceiveMessage ( ) ( byte , [ ] byte , error ) {
select {
case err := <- c . chError :
case err := <- c . errorCh :
return 0 , nil , err
case <- c . chC lose :
case <- c . closeCh :
return 0 , nil , io . EOF
case msg := <- c . chReceive :
case msg := <- c . receiveCh :
return msg . channelID , msg . payload , nil
}
}
// NodeInfo implements Connection.
func ( c * mConnConnection ) NodeInfo ( ) NodeInfo {
return c . peerInfo
}
// PubKey implements Connection.
func ( c * mConnConnection ) PubKey ( ) crypto . PubKey {
return c . secretConn . RemotePubKey ( )
}
// LocalEndpoint implements Connection.
func ( c * mConnConnection ) LocalEndpoint ( ) Endpoint {
// FIXME: For compatibility with existing P2P tests we need to
// handle non-TCP connections. This should be removed.
endpoint := Endpoint {
Protocol : MConnProtocol ,
PeerID : c . transport . nodeInfo . ID ( ) ,
}
if addr , ok := c . se cretC onn. LocalAddr ( ) . ( * net . TCPAddr ) ; ok {
if addr , ok := c . conn . LocalAddr ( ) . ( * net . TCPAddr ) ; ok {
endpoint . IP = addr . IP
endpoint . Port = uint16 ( addr . Port )
}
@ -642,13 +394,10 @@ func (c *mConnConnection) LocalEndpoint() Endpoint {
// RemoteEndpoint implements Connection.
func ( c * mConnConnection ) RemoteEndpoint ( ) Endpoint {
// FIXME: For compatibility with existing P2P tests we need to
// handle non-TCP connections. This should be removed.
endpoint := Endpoint {
Protocol : MConnProtocol ,
PeerID : c . peerInfo . ID ( ) ,
}
if addr , ok := c . se cretC onn. RemoteAddr ( ) . ( * net . TCPAddr ) ; ok {
if addr , ok := c . conn . RemoteAddr ( ) . ( * net . TCPAddr ) ; ok {
endpoint . IP = addr . IP
endpoint . Port = uint16 ( addr . Port )
}
@ -657,26 +406,36 @@ func (c *mConnConnection) RemoteEndpoint() Endpoint {
// Status implements Connection.
func ( c * mConnConnection ) Status ( ) conn . ConnectionStatus {
return c . mConn . Status ( )
if c . mconn == nil {
return conn . ConnectionStatus { }
}
return c . mconn . Status ( )
}
// Close implements Connection.
func ( c * mConnConnection ) Close ( ) error {
c . transport . conns . RemoveAddr ( c . secretConn . RemoteAddr ( ) )
var err error
c . closeOnce . Do ( func ( ) {
err = c . mConn . Stop ( )
close ( c . chClose )
if c . mconn != nil {
err = c . mconn . Stop ( )
} else {
err = c . conn . Close ( )
}
close ( c . closeCh )
} )
return err
}
// FlushClose implements Connection.
func ( c * mConnConnection ) FlushClose ( ) error {
c . transport . conns . RemoveAddr ( c . secretConn . RemoteAddr ( ) )
var err error
c . closeOnce . Do ( func ( ) {
c . mConn . FlushStop ( )
close ( c . chClose )
if c . mconn != nil {
c . mconn . FlushStop ( )
} else {
err = c . conn . Close ( )
}
close ( c . closeCh )
} )
return nil
return err
}