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

682 lines
18 KiB

p2p: implement new Transport interface (#5791) This implements a new `Transport` interface and related types for the P2P refactor in #5670. Previously, `conn.MConnection` was very tightly coupled to the `Peer` implementation -- in order to allow alternative non-multiplexed transports (e.g. QUIC), MConnection has now been moved below the `Transport` interface, as `MConnTransport`, and decoupled from the peer. Since the `p2p` package is not covered by our Go API stability, this is not considered a breaking change, and not listed in the changelog. The initial approach was to implement the new interface in its final form (which also involved possible protocol changes, see https://github.com/tendermint/spec/pull/227). However, it turned out that this would require a large amount of changes to existing P2P code because of the previous tight coupling between `Peer` and `MConnection` and the reliance on subtleties in the MConnection behavior. Instead, I have broadened the `Transport` interface to expose much of the existing MConnection interface, preserved much of the existing MConnection logic and behavior in the transport implementation, and tried to make as few changes to the rest of the P2P stack as possible. We will instead reduce this interface gradually as we refactor other parts of the P2P stack. The low-level transport code and protocol (e.g. MConnection, SecretConnection and so on) has not been significantly changed, and refactoring this is not a priority until we come up with a plan for QUIC adoption, as we may end up discarding the MConnection code entirely. There are no tests of the new `MConnTransport`, as this code is likely to evolve as we proceed with the P2P refactor, but tests should be added before a final release. The E2E tests are sufficient for basic validation in the meanwhile.
4 years ago
p2p: implement new Transport interface (#5791) This implements a new `Transport` interface and related types for the P2P refactor in #5670. Previously, `conn.MConnection` was very tightly coupled to the `Peer` implementation -- in order to allow alternative non-multiplexed transports (e.g. QUIC), MConnection has now been moved below the `Transport` interface, as `MConnTransport`, and decoupled from the peer. Since the `p2p` package is not covered by our Go API stability, this is not considered a breaking change, and not listed in the changelog. The initial approach was to implement the new interface in its final form (which also involved possible protocol changes, see https://github.com/tendermint/spec/pull/227). However, it turned out that this would require a large amount of changes to existing P2P code because of the previous tight coupling between `Peer` and `MConnection` and the reliance on subtleties in the MConnection behavior. Instead, I have broadened the `Transport` interface to expose much of the existing MConnection interface, preserved much of the existing MConnection logic and behavior in the transport implementation, and tried to make as few changes to the rest of the P2P stack as possible. We will instead reduce this interface gradually as we refactor other parts of the P2P stack. The low-level transport code and protocol (e.g. MConnection, SecretConnection and so on) has not been significantly changed, and refactoring this is not a priority until we come up with a plan for QUIC adoption, as we may end up discarding the MConnection code entirely. There are no tests of the new `MConnTransport`, as this code is likely to evolve as we proceed with the P2P refactor, but tests should be added before a final release. The E2E tests are sufficient for basic validation in the meanwhile.
4 years ago
p2p: implement new Transport interface (#5791) This implements a new `Transport` interface and related types for the P2P refactor in #5670. Previously, `conn.MConnection` was very tightly coupled to the `Peer` implementation -- in order to allow alternative non-multiplexed transports (e.g. QUIC), MConnection has now been moved below the `Transport` interface, as `MConnTransport`, and decoupled from the peer. Since the `p2p` package is not covered by our Go API stability, this is not considered a breaking change, and not listed in the changelog. The initial approach was to implement the new interface in its final form (which also involved possible protocol changes, see https://github.com/tendermint/spec/pull/227). However, it turned out that this would require a large amount of changes to existing P2P code because of the previous tight coupling between `Peer` and `MConnection` and the reliance on subtleties in the MConnection behavior. Instead, I have broadened the `Transport` interface to expose much of the existing MConnection interface, preserved much of the existing MConnection logic and behavior in the transport implementation, and tried to make as few changes to the rest of the P2P stack as possible. We will instead reduce this interface gradually as we refactor other parts of the P2P stack. The low-level transport code and protocol (e.g. MConnection, SecretConnection and so on) has not been significantly changed, and refactoring this is not a priority until we come up with a plan for QUIC adoption, as we may end up discarding the MConnection code entirely. There are no tests of the new `MConnTransport`, as this code is likely to evolve as we proceed with the P2P refactor, but tests should be added before a final release. The E2E tests are sufficient for basic validation in the meanwhile.
4 years ago
p2p: implement new Transport interface (#5791) This implements a new `Transport` interface and related types for the P2P refactor in #5670. Previously, `conn.MConnection` was very tightly coupled to the `Peer` implementation -- in order to allow alternative non-multiplexed transports (e.g. QUIC), MConnection has now been moved below the `Transport` interface, as `MConnTransport`, and decoupled from the peer. Since the `p2p` package is not covered by our Go API stability, this is not considered a breaking change, and not listed in the changelog. The initial approach was to implement the new interface in its final form (which also involved possible protocol changes, see https://github.com/tendermint/spec/pull/227). However, it turned out that this would require a large amount of changes to existing P2P code because of the previous tight coupling between `Peer` and `MConnection` and the reliance on subtleties in the MConnection behavior. Instead, I have broadened the `Transport` interface to expose much of the existing MConnection interface, preserved much of the existing MConnection logic and behavior in the transport implementation, and tried to make as few changes to the rest of the P2P stack as possible. We will instead reduce this interface gradually as we refactor other parts of the P2P stack. The low-level transport code and protocol (e.g. MConnection, SecretConnection and so on) has not been significantly changed, and refactoring this is not a priority until we come up with a plan for QUIC adoption, as we may end up discarding the MConnection code entirely. There are no tests of the new `MConnTransport`, as this code is likely to evolve as we proceed with the P2P refactor, but tests should be added before a final release. The E2E tests are sufficient for basic validation in the meanwhile.
4 years ago
p2p: implement new Transport interface (#5791) This implements a new `Transport` interface and related types for the P2P refactor in #5670. Previously, `conn.MConnection` was very tightly coupled to the `Peer` implementation -- in order to allow alternative non-multiplexed transports (e.g. QUIC), MConnection has now been moved below the `Transport` interface, as `MConnTransport`, and decoupled from the peer. Since the `p2p` package is not covered by our Go API stability, this is not considered a breaking change, and not listed in the changelog. The initial approach was to implement the new interface in its final form (which also involved possible protocol changes, see https://github.com/tendermint/spec/pull/227). However, it turned out that this would require a large amount of changes to existing P2P code because of the previous tight coupling between `Peer` and `MConnection` and the reliance on subtleties in the MConnection behavior. Instead, I have broadened the `Transport` interface to expose much of the existing MConnection interface, preserved much of the existing MConnection logic and behavior in the transport implementation, and tried to make as few changes to the rest of the P2P stack as possible. We will instead reduce this interface gradually as we refactor other parts of the P2P stack. The low-level transport code and protocol (e.g. MConnection, SecretConnection and so on) has not been significantly changed, and refactoring this is not a priority until we come up with a plan for QUIC adoption, as we may end up discarding the MConnection code entirely. There are no tests of the new `MConnTransport`, as this code is likely to evolve as we proceed with the P2P refactor, but tests should be added before a final release. The E2E tests are sufficient for basic validation in the meanwhile.
4 years ago
p2p: make PeerManager.DialNext() and EvictNext() block (#5947) See #5936 and #5938 for background. The plan was initially to have `DialNext()` and `EvictNext()` return a channel. However, implementing this became unnecessarily complicated and error-prone. As an example, the channel would be both consumed and populated (via method calls) by the same driving method (e.g. `Router.dialPeers()`) which could easily cause deadlocks where a method call blocked while sending on the channel that the caller itself was responsible for consuming (but couldn't since it was busy making the method call). It would also require a set of goroutines in the peer manager that would interact with the goroutines in the router in non-obvious ways, and fully populating the channel on startup could cause deadlocks with other startup tasks. Several issues like these made the solution hard to reason about. I therefore simply made `DialNext()` and `EvictNext()` block until the next peer was available, using internal triggers to wake these methods up in a non-blocking fashion when any relevant state changes occurred. This proved much simpler to reason about, since there are no goroutines in the peer manager (except for trivial retry timers), nor any blocking channel sends, and it instead relies entirely on the existing goroutine structure of the router for concurrency. This also happens to be the same pattern used by the `Transport.Accept()` API, following Go stdlib conventions, so all router goroutines end up using a consistent pattern as well.
3 years ago
p2p: implement new Transport interface (#5791) This implements a new `Transport` interface and related types for the P2P refactor in #5670. Previously, `conn.MConnection` was very tightly coupled to the `Peer` implementation -- in order to allow alternative non-multiplexed transports (e.g. QUIC), MConnection has now been moved below the `Transport` interface, as `MConnTransport`, and decoupled from the peer. Since the `p2p` package is not covered by our Go API stability, this is not considered a breaking change, and not listed in the changelog. The initial approach was to implement the new interface in its final form (which also involved possible protocol changes, see https://github.com/tendermint/spec/pull/227). However, it turned out that this would require a large amount of changes to existing P2P code because of the previous tight coupling between `Peer` and `MConnection` and the reliance on subtleties in the MConnection behavior. Instead, I have broadened the `Transport` interface to expose much of the existing MConnection interface, preserved much of the existing MConnection logic and behavior in the transport implementation, and tried to make as few changes to the rest of the P2P stack as possible. We will instead reduce this interface gradually as we refactor other parts of the P2P stack. The low-level transport code and protocol (e.g. MConnection, SecretConnection and so on) has not been significantly changed, and refactoring this is not a priority until we come up with a plan for QUIC adoption, as we may end up discarding the MConnection code entirely. There are no tests of the new `MConnTransport`, as this code is likely to evolve as we proceed with the P2P refactor, but tests should be added before a final release. The E2E tests are sufficient for basic validation in the meanwhile.
4 years ago
p2p: implement new Transport interface (#5791) This implements a new `Transport` interface and related types for the P2P refactor in #5670. Previously, `conn.MConnection` was very tightly coupled to the `Peer` implementation -- in order to allow alternative non-multiplexed transports (e.g. QUIC), MConnection has now been moved below the `Transport` interface, as `MConnTransport`, and decoupled from the peer. Since the `p2p` package is not covered by our Go API stability, this is not considered a breaking change, and not listed in the changelog. The initial approach was to implement the new interface in its final form (which also involved possible protocol changes, see https://github.com/tendermint/spec/pull/227). However, it turned out that this would require a large amount of changes to existing P2P code because of the previous tight coupling between `Peer` and `MConnection` and the reliance on subtleties in the MConnection behavior. Instead, I have broadened the `Transport` interface to expose much of the existing MConnection interface, preserved much of the existing MConnection logic and behavior in the transport implementation, and tried to make as few changes to the rest of the P2P stack as possible. We will instead reduce this interface gradually as we refactor other parts of the P2P stack. The low-level transport code and protocol (e.g. MConnection, SecretConnection and so on) has not been significantly changed, and refactoring this is not a priority until we come up with a plan for QUIC adoption, as we may end up discarding the MConnection code entirely. There are no tests of the new `MConnTransport`, as this code is likely to evolve as we proceed with the P2P refactor, but tests should be added before a final release. The E2E tests are sufficient for basic validation in the meanwhile.
4 years ago
p2p: implement new Transport interface (#5791) This implements a new `Transport` interface and related types for the P2P refactor in #5670. Previously, `conn.MConnection` was very tightly coupled to the `Peer` implementation -- in order to allow alternative non-multiplexed transports (e.g. QUIC), MConnection has now been moved below the `Transport` interface, as `MConnTransport`, and decoupled from the peer. Since the `p2p` package is not covered by our Go API stability, this is not considered a breaking change, and not listed in the changelog. The initial approach was to implement the new interface in its final form (which also involved possible protocol changes, see https://github.com/tendermint/spec/pull/227). However, it turned out that this would require a large amount of changes to existing P2P code because of the previous tight coupling between `Peer` and `MConnection` and the reliance on subtleties in the MConnection behavior. Instead, I have broadened the `Transport` interface to expose much of the existing MConnection interface, preserved much of the existing MConnection logic and behavior in the transport implementation, and tried to make as few changes to the rest of the P2P stack as possible. We will instead reduce this interface gradually as we refactor other parts of the P2P stack. The low-level transport code and protocol (e.g. MConnection, SecretConnection and so on) has not been significantly changed, and refactoring this is not a priority until we come up with a plan for QUIC adoption, as we may end up discarding the MConnection code entirely. There are no tests of the new `MConnTransport`, as this code is likely to evolve as we proceed with the P2P refactor, but tests should be added before a final release. The E2E tests are sufficient for basic validation in the meanwhile.
4 years ago
p2p: implement new Transport interface (#5791) This implements a new `Transport` interface and related types for the P2P refactor in #5670. Previously, `conn.MConnection` was very tightly coupled to the `Peer` implementation -- in order to allow alternative non-multiplexed transports (e.g. QUIC), MConnection has now been moved below the `Transport` interface, as `MConnTransport`, and decoupled from the peer. Since the `p2p` package is not covered by our Go API stability, this is not considered a breaking change, and not listed in the changelog. The initial approach was to implement the new interface in its final form (which also involved possible protocol changes, see https://github.com/tendermint/spec/pull/227). However, it turned out that this would require a large amount of changes to existing P2P code because of the previous tight coupling between `Peer` and `MConnection` and the reliance on subtleties in the MConnection behavior. Instead, I have broadened the `Transport` interface to expose much of the existing MConnection interface, preserved much of the existing MConnection logic and behavior in the transport implementation, and tried to make as few changes to the rest of the P2P stack as possible. We will instead reduce this interface gradually as we refactor other parts of the P2P stack. The low-level transport code and protocol (e.g. MConnection, SecretConnection and so on) has not been significantly changed, and refactoring this is not a priority until we come up with a plan for QUIC adoption, as we may end up discarding the MConnection code entirely. There are no tests of the new `MConnTransport`, as this code is likely to evolve as we proceed with the P2P refactor, but tests should be added before a final release. The E2E tests are sufficient for basic validation in the meanwhile.
4 years ago
p2p: implement new Transport interface (#5791) This implements a new `Transport` interface and related types for the P2P refactor in #5670. Previously, `conn.MConnection` was very tightly coupled to the `Peer` implementation -- in order to allow alternative non-multiplexed transports (e.g. QUIC), MConnection has now been moved below the `Transport` interface, as `MConnTransport`, and decoupled from the peer. Since the `p2p` package is not covered by our Go API stability, this is not considered a breaking change, and not listed in the changelog. The initial approach was to implement the new interface in its final form (which also involved possible protocol changes, see https://github.com/tendermint/spec/pull/227). However, it turned out that this would require a large amount of changes to existing P2P code because of the previous tight coupling between `Peer` and `MConnection` and the reliance on subtleties in the MConnection behavior. Instead, I have broadened the `Transport` interface to expose much of the existing MConnection interface, preserved much of the existing MConnection logic and behavior in the transport implementation, and tried to make as few changes to the rest of the P2P stack as possible. We will instead reduce this interface gradually as we refactor other parts of the P2P stack. The low-level transport code and protocol (e.g. MConnection, SecretConnection and so on) has not been significantly changed, and refactoring this is not a priority until we come up with a plan for QUIC adoption, as we may end up discarding the MConnection code entirely. There are no tests of the new `MConnTransport`, as this code is likely to evolve as we proceed with the P2P refactor, but tests should be added before a final release. The E2E tests are sufficient for basic validation in the meanwhile.
4 years ago
p2p: implement new Transport interface (#5791) This implements a new `Transport` interface and related types for the P2P refactor in #5670. Previously, `conn.MConnection` was very tightly coupled to the `Peer` implementation -- in order to allow alternative non-multiplexed transports (e.g. QUIC), MConnection has now been moved below the `Transport` interface, as `MConnTransport`, and decoupled from the peer. Since the `p2p` package is not covered by our Go API stability, this is not considered a breaking change, and not listed in the changelog. The initial approach was to implement the new interface in its final form (which also involved possible protocol changes, see https://github.com/tendermint/spec/pull/227). However, it turned out that this would require a large amount of changes to existing P2P code because of the previous tight coupling between `Peer` and `MConnection` and the reliance on subtleties in the MConnection behavior. Instead, I have broadened the `Transport` interface to expose much of the existing MConnection interface, preserved much of the existing MConnection logic and behavior in the transport implementation, and tried to make as few changes to the rest of the P2P stack as possible. We will instead reduce this interface gradually as we refactor other parts of the P2P stack. The low-level transport code and protocol (e.g. MConnection, SecretConnection and so on) has not been significantly changed, and refactoring this is not a priority until we come up with a plan for QUIC adoption, as we may end up discarding the MConnection code entirely. There are no tests of the new `MConnTransport`, as this code is likely to evolve as we proceed with the P2P refactor, but tests should be added before a final release. The E2E tests are sufficient for basic validation in the meanwhile.
4 years ago
p2p: implement new Transport interface (#5791) This implements a new `Transport` interface and related types for the P2P refactor in #5670. Previously, `conn.MConnection` was very tightly coupled to the `Peer` implementation -- in order to allow alternative non-multiplexed transports (e.g. QUIC), MConnection has now been moved below the `Transport` interface, as `MConnTransport`, and decoupled from the peer. Since the `p2p` package is not covered by our Go API stability, this is not considered a breaking change, and not listed in the changelog. The initial approach was to implement the new interface in its final form (which also involved possible protocol changes, see https://github.com/tendermint/spec/pull/227). However, it turned out that this would require a large amount of changes to existing P2P code because of the previous tight coupling between `Peer` and `MConnection` and the reliance on subtleties in the MConnection behavior. Instead, I have broadened the `Transport` interface to expose much of the existing MConnection interface, preserved much of the existing MConnection logic and behavior in the transport implementation, and tried to make as few changes to the rest of the P2P stack as possible. We will instead reduce this interface gradually as we refactor other parts of the P2P stack. The low-level transport code and protocol (e.g. MConnection, SecretConnection and so on) has not been significantly changed, and refactoring this is not a priority until we come up with a plan for QUIC adoption, as we may end up discarding the MConnection code entirely. There are no tests of the new `MConnTransport`, as this code is likely to evolve as we proceed with the P2P refactor, but tests should be added before a final release. The E2E tests are sufficient for basic validation in the meanwhile.
4 years ago
p2p: implement new Transport interface (#5791) This implements a new `Transport` interface and related types for the P2P refactor in #5670. Previously, `conn.MConnection` was very tightly coupled to the `Peer` implementation -- in order to allow alternative non-multiplexed transports (e.g. QUIC), MConnection has now been moved below the `Transport` interface, as `MConnTransport`, and decoupled from the peer. Since the `p2p` package is not covered by our Go API stability, this is not considered a breaking change, and not listed in the changelog. The initial approach was to implement the new interface in its final form (which also involved possible protocol changes, see https://github.com/tendermint/spec/pull/227). However, it turned out that this would require a large amount of changes to existing P2P code because of the previous tight coupling between `Peer` and `MConnection` and the reliance on subtleties in the MConnection behavior. Instead, I have broadened the `Transport` interface to expose much of the existing MConnection interface, preserved much of the existing MConnection logic and behavior in the transport implementation, and tried to make as few changes to the rest of the P2P stack as possible. We will instead reduce this interface gradually as we refactor other parts of the P2P stack. The low-level transport code and protocol (e.g. MConnection, SecretConnection and so on) has not been significantly changed, and refactoring this is not a priority until we come up with a plan for QUIC adoption, as we may end up discarding the MConnection code entirely. There are no tests of the new `MConnTransport`, as this code is likely to evolve as we proceed with the P2P refactor, but tests should be added before a final release. The E2E tests are sufficient for basic validation in the meanwhile.
4 years ago
p2p: implement new Transport interface (#5791) This implements a new `Transport` interface and related types for the P2P refactor in #5670. Previously, `conn.MConnection` was very tightly coupled to the `Peer` implementation -- in order to allow alternative non-multiplexed transports (e.g. QUIC), MConnection has now been moved below the `Transport` interface, as `MConnTransport`, and decoupled from the peer. Since the `p2p` package is not covered by our Go API stability, this is not considered a breaking change, and not listed in the changelog. The initial approach was to implement the new interface in its final form (which also involved possible protocol changes, see https://github.com/tendermint/spec/pull/227). However, it turned out that this would require a large amount of changes to existing P2P code because of the previous tight coupling between `Peer` and `MConnection` and the reliance on subtleties in the MConnection behavior. Instead, I have broadened the `Transport` interface to expose much of the existing MConnection interface, preserved much of the existing MConnection logic and behavior in the transport implementation, and tried to make as few changes to the rest of the P2P stack as possible. We will instead reduce this interface gradually as we refactor other parts of the P2P stack. The low-level transport code and protocol (e.g. MConnection, SecretConnection and so on) has not been significantly changed, and refactoring this is not a priority until we come up with a plan for QUIC adoption, as we may end up discarding the MConnection code entirely. There are no tests of the new `MConnTransport`, as this code is likely to evolve as we proceed with the P2P refactor, but tests should be added before a final release. The E2E tests are sufficient for basic validation in the meanwhile.
4 years ago
p2p: implement new Transport interface (#5791) This implements a new `Transport` interface and related types for the P2P refactor in #5670. Previously, `conn.MConnection` was very tightly coupled to the `Peer` implementation -- in order to allow alternative non-multiplexed transports (e.g. QUIC), MConnection has now been moved below the `Transport` interface, as `MConnTransport`, and decoupled from the peer. Since the `p2p` package is not covered by our Go API stability, this is not considered a breaking change, and not listed in the changelog. The initial approach was to implement the new interface in its final form (which also involved possible protocol changes, see https://github.com/tendermint/spec/pull/227). However, it turned out that this would require a large amount of changes to existing P2P code because of the previous tight coupling between `Peer` and `MConnection` and the reliance on subtleties in the MConnection behavior. Instead, I have broadened the `Transport` interface to expose much of the existing MConnection interface, preserved much of the existing MConnection logic and behavior in the transport implementation, and tried to make as few changes to the rest of the P2P stack as possible. We will instead reduce this interface gradually as we refactor other parts of the P2P stack. The low-level transport code and protocol (e.g. MConnection, SecretConnection and so on) has not been significantly changed, and refactoring this is not a priority until we come up with a plan for QUIC adoption, as we may end up discarding the MConnection code entirely. There are no tests of the new `MConnTransport`, as this code is likely to evolve as we proceed with the P2P refactor, but tests should be added before a final release. The E2E tests are sufficient for basic validation in the meanwhile.
4 years ago
  1. package p2p
  2. import (
  3. "context"
  4. "errors"
  5. "fmt"
  6. "io"
  7. "net"
  8. "sync"
  9. "time"
  10. "github.com/tendermint/tendermint/crypto"
  11. "github.com/tendermint/tendermint/libs/log"
  12. "github.com/tendermint/tendermint/libs/protoio"
  13. "github.com/tendermint/tendermint/p2p/conn"
  14. p2pproto "github.com/tendermint/tendermint/proto/tendermint/p2p"
  15. "golang.org/x/net/netutil"
  16. )
  17. const (
  18. defaultDialTimeout = time.Second
  19. defaultFilterTimeout = 5 * time.Second
  20. defaultHandshakeTimeout = 3 * time.Second
  21. )
  22. // MConnProtocol is the MConn protocol identifier.
  23. const MConnProtocol Protocol = "mconn"
  24. // MConnTransportOption sets an option for MConnTransport.
  25. type MConnTransportOption func(*MConnTransport)
  26. // MConnTransportMaxIncomingConnections sets the maximum number of
  27. // simultaneous incoming connections. Default: 0 (unlimited)
  28. func MConnTransportMaxIncomingConnections(max int) MConnTransportOption {
  29. return func(mt *MConnTransport) { mt.maxIncomingConnections = max }
  30. }
  31. // MConnTransportFilterTimeout sets the timeout for filter callbacks.
  32. func MConnTransportFilterTimeout(timeout time.Duration) MConnTransportOption {
  33. return func(mt *MConnTransport) { mt.filterTimeout = timeout }
  34. }
  35. // MConnTransportConnFilters sets connection filters.
  36. func MConnTransportConnFilters(filters ...ConnFilterFunc) MConnTransportOption {
  37. return func(mt *MConnTransport) { mt.connFilters = filters }
  38. }
  39. // ConnFilterFunc is a callback for connection filtering. If it returns an
  40. // error, the connection is rejected. The set of existing connections is passed
  41. // along with the new connection and all resolved IPs.
  42. type ConnFilterFunc func(ConnSet, net.Conn, []net.IP) error
  43. // ConnDuplicateIPFilter resolves and keeps all ips for an incoming connection
  44. // and refuses new ones if they come from a known ip.
  45. var ConnDuplicateIPFilter ConnFilterFunc = func(cs ConnSet, c net.Conn, ips []net.IP) error {
  46. for _, ip := range ips {
  47. if cs.HasIP(ip) {
  48. return ErrRejected{
  49. conn: c,
  50. err: fmt.Errorf("ip<%v> already connected", ip),
  51. isDuplicate: true,
  52. }
  53. }
  54. }
  55. return nil
  56. }
  57. // MConnTransport is a Transport implementation using the current multiplexed
  58. // Tendermint protocol ("MConn"). It inherits lots of code and logic from the
  59. // previous implementation for parity with the current P2P stack (such as
  60. // connection filtering, peer verification, and panic handling), which should be
  61. // moved out of the transport once the rest of the P2P stack is rewritten.
  62. type MConnTransport struct {
  63. privKey crypto.PrivKey
  64. nodeInfo NodeInfo
  65. channelDescs []*ChannelDescriptor
  66. mConnConfig conn.MConnConfig
  67. maxIncomingConnections int
  68. dialTimeout time.Duration
  69. handshakeTimeout time.Duration
  70. filterTimeout time.Duration
  71. logger log.Logger
  72. listener net.Listener
  73. closeOnce sync.Once
  74. chAccept chan *mConnConnection
  75. chError chan error
  76. chClose chan struct{}
  77. // FIXME: This is a vestige from the old transport, and should be managed
  78. // by the router once we rewrite the P2P core.
  79. conns ConnSet
  80. connFilters []ConnFilterFunc
  81. }
  82. // NewMConnTransport sets up a new MConn transport.
  83. func NewMConnTransport(
  84. logger log.Logger,
  85. nodeInfo NodeInfo,
  86. privKey crypto.PrivKey,
  87. mConnConfig conn.MConnConfig,
  88. opts ...MConnTransportOption,
  89. ) *MConnTransport {
  90. m := &MConnTransport{
  91. privKey: privKey,
  92. nodeInfo: nodeInfo,
  93. mConnConfig: mConnConfig,
  94. channelDescs: []*ChannelDescriptor{},
  95. dialTimeout: defaultDialTimeout,
  96. handshakeTimeout: defaultHandshakeTimeout,
  97. filterTimeout: defaultFilterTimeout,
  98. logger: logger,
  99. chAccept: make(chan *mConnConnection),
  100. chError: make(chan error),
  101. chClose: make(chan struct{}),
  102. conns: NewConnSet(),
  103. connFilters: []ConnFilterFunc{},
  104. }
  105. for _, opt := range opts {
  106. opt(m)
  107. }
  108. return m
  109. }
  110. // SetChannelDescriptors implements Transport.
  111. //
  112. // This is not concurrency-safe, and must be called before listening.
  113. //
  114. // FIXME: This is here for compatibility with existing switch code,
  115. // it should be passed via the constructor instead.
  116. func (m *MConnTransport) SetChannelDescriptors(chDescs []*conn.ChannelDescriptor) {
  117. m.channelDescs = chDescs
  118. }
  119. // Listen asynchronously listens for inbound connections on the given endpoint.
  120. // It must be called exactly once before calling Accept(), and the caller must
  121. // call Close() to shut down the listener.
  122. func (m *MConnTransport) Listen(endpoint Endpoint) error {
  123. if m.listener != nil {
  124. return errors.New("MConn transport is already listening")
  125. }
  126. err := m.normalizeEndpoint(&endpoint)
  127. if err != nil {
  128. return fmt.Errorf("invalid MConn listen endpoint %q: %w", endpoint, err)
  129. }
  130. m.listener, err = net.Listen("tcp", fmt.Sprintf("%v:%v", endpoint.IP, endpoint.Port))
  131. if err != nil {
  132. return err
  133. }
  134. if m.maxIncomingConnections > 0 {
  135. m.listener = netutil.LimitListener(m.listener, m.maxIncomingConnections)
  136. }
  137. // Spawn a goroutine to accept inbound connections asynchronously.
  138. go m.accept()
  139. return nil
  140. }
  141. // accept accepts inbound connections in a loop, and asynchronously handshakes
  142. // with the peer to avoid head-of-line blocking. Established connections are
  143. // passed to Accept() via chAccept.
  144. // See: https://github.com/tendermint/tendermint/issues/204
  145. func (m *MConnTransport) accept() {
  146. for {
  147. tcpConn, err := m.listener.Accept()
  148. if err != nil {
  149. // We have to check for closure first, since we don't want to
  150. // propagate "use of closed network connection" errors.
  151. select {
  152. case <-m.chClose:
  153. default:
  154. // We also select on chClose here, in case the transport closes
  155. // while we're blocked on error propagation.
  156. select {
  157. case m.chError <- err:
  158. case <-m.chClose:
  159. }
  160. }
  161. return
  162. }
  163. go func() {
  164. err := m.filterTCPConn(tcpConn)
  165. if err != nil {
  166. if err := tcpConn.Close(); err != nil {
  167. m.logger.Debug("failed to close TCP connection", "err", err)
  168. }
  169. select {
  170. case m.chError <- err:
  171. case <-m.chClose:
  172. }
  173. return
  174. }
  175. conn, err := newMConnConnection(m, tcpConn, "")
  176. if err != nil {
  177. m.conns.Remove(tcpConn)
  178. if err := tcpConn.Close(); err != nil {
  179. m.logger.Debug("failed to close TCP connection", "err", err)
  180. }
  181. select {
  182. case m.chError <- err:
  183. case <-m.chClose:
  184. }
  185. } else {
  186. select {
  187. case m.chAccept <- conn:
  188. case <-m.chClose:
  189. if err := tcpConn.Close(); err != nil {
  190. m.logger.Debug("failed to close TCP connection", "err", err)
  191. }
  192. }
  193. }
  194. }()
  195. }
  196. }
  197. // Accept implements Transport.
  198. //
  199. // accept() runs a concurrent accept loop that accepts inbound connections
  200. // and then handshakes in a non-blocking fashion. The handshaked and validated
  201. // connections are returned via this call, picking them off of the chAccept
  202. // channel (or the handshake error, if any).
  203. func (m *MConnTransport) Accept(ctx context.Context) (Connection, error) {
  204. select {
  205. case conn := <-m.chAccept:
  206. return conn, nil
  207. case err := <-m.chError:
  208. return nil, err
  209. case <-m.chClose:
  210. return nil, ErrTransportClosed{}
  211. case <-ctx.Done():
  212. return nil, ctx.Err()
  213. }
  214. }
  215. // Dial implements Transport.
  216. func (m *MConnTransport) Dial(ctx context.Context, endpoint Endpoint) (Connection, error) {
  217. err := m.normalizeEndpoint(&endpoint)
  218. if err != nil {
  219. return nil, err
  220. }
  221. ctx, cancel := context.WithTimeout(ctx, m.dialTimeout)
  222. defer cancel()
  223. dialer := net.Dialer{}
  224. tcpConn, err := dialer.DialContext(ctx, "tcp",
  225. net.JoinHostPort(endpoint.IP.String(), fmt.Sprintf("%v", endpoint.Port)))
  226. if err != nil {
  227. return nil, err
  228. }
  229. err = m.filterTCPConn(tcpConn)
  230. if err != nil {
  231. if err := tcpConn.Close(); err != nil {
  232. m.logger.Debug("failed to close TCP connection", "err", err)
  233. }
  234. return nil, err
  235. }
  236. conn, err := newMConnConnection(m, tcpConn, endpoint.PeerID)
  237. if err != nil {
  238. m.conns.Remove(tcpConn)
  239. if err := tcpConn.Close(); err != nil {
  240. m.logger.Debug("failed to close TCP connection", "err", err)
  241. }
  242. return nil, err
  243. }
  244. return conn, nil
  245. }
  246. // Endpoints implements Transport.
  247. func (m *MConnTransport) Endpoints() []Endpoint {
  248. if m.listener == nil {
  249. return []Endpoint{}
  250. }
  251. addr := m.listener.Addr().(*net.TCPAddr)
  252. return []Endpoint{{
  253. Protocol: MConnProtocol,
  254. PeerID: m.nodeInfo.ID(),
  255. IP: addr.IP,
  256. Port: uint16(addr.Port),
  257. }}
  258. }
  259. // Close implements Transport.
  260. func (m *MConnTransport) Close() error {
  261. var err error
  262. m.closeOnce.Do(func() {
  263. // We have to close chClose first, so that accept() will detect
  264. // the closure and not propagate the error.
  265. close(m.chClose)
  266. if m.listener != nil {
  267. err = m.listener.Close()
  268. }
  269. })
  270. return err
  271. }
  272. // filterTCPConn filters a TCP connection, rejecting it if this function errors.
  273. func (m *MConnTransport) filterTCPConn(tcpConn net.Conn) error {
  274. if m.conns.Has(tcpConn) {
  275. return ErrRejected{conn: tcpConn, isDuplicate: true}
  276. }
  277. host, _, err := net.SplitHostPort(tcpConn.RemoteAddr().String())
  278. if err != nil {
  279. return err
  280. }
  281. ip := net.ParseIP(host)
  282. if ip == nil {
  283. return fmt.Errorf("connection address has invalid IP address %q", host)
  284. }
  285. // Apply filter callbacks.
  286. chErr := make(chan error, len(m.connFilters))
  287. for _, connFilter := range m.connFilters {
  288. go func(connFilter ConnFilterFunc) {
  289. chErr <- connFilter(m.conns, tcpConn, []net.IP{ip})
  290. }(connFilter)
  291. }
  292. for i := 0; i < cap(chErr); i++ {
  293. select {
  294. case err := <-chErr:
  295. if err != nil {
  296. return ErrRejected{conn: tcpConn, err: err, isFiltered: true}
  297. }
  298. case <-time.After(m.filterTimeout):
  299. return ErrFilterTimeout{}
  300. }
  301. }
  302. // FIXME: Doesn't really make sense to set this here, but we preserve the
  303. // behavior from the previous P2P transport implementation. This should
  304. // be moved to the router.
  305. m.conns.Set(tcpConn, []net.IP{ip})
  306. return nil
  307. }
  308. // normalizeEndpoint normalizes and validates an endpoint.
  309. func (m *MConnTransport) normalizeEndpoint(endpoint *Endpoint) error {
  310. if endpoint == nil {
  311. return errors.New("nil endpoint")
  312. }
  313. if err := endpoint.Validate(); err != nil {
  314. return err
  315. }
  316. if endpoint.Protocol == "" {
  317. endpoint.Protocol = MConnProtocol
  318. }
  319. if endpoint.Protocol != MConnProtocol {
  320. return fmt.Errorf("unsupported protocol %q", endpoint.Protocol)
  321. }
  322. if len(endpoint.IP) == 0 {
  323. return errors.New("endpoint must have an IP address")
  324. }
  325. if endpoint.Path != "" {
  326. return fmt.Errorf("endpoint cannot have path (got %q)", endpoint.Path)
  327. }
  328. if endpoint.Port == 0 {
  329. endpoint.Port = 26657
  330. }
  331. return nil
  332. }
  333. // mConnConnection implements Connection for MConnTransport. It takes a base TCP
  334. // connection and upgrades it to MConnection over an encrypted SecretConnection.
  335. type mConnConnection struct {
  336. logger log.Logger
  337. transport *MConnTransport
  338. secretConn *conn.SecretConnection
  339. mConn *conn.MConnection
  340. peerInfo NodeInfo
  341. closeOnce sync.Once
  342. chReceive chan mConnMessage
  343. chError chan error
  344. chClose chan struct{}
  345. }
  346. // mConnMessage passes MConnection messages through internal channels.
  347. type mConnMessage struct {
  348. channelID byte
  349. payload []byte
  350. }
  351. // newMConnConnection creates a new mConnConnection by handshaking
  352. // with a peer.
  353. func newMConnConnection(
  354. transport *MConnTransport,
  355. tcpConn net.Conn,
  356. expectPeerID NodeID,
  357. ) (c *mConnConnection, err error) {
  358. // FIXME: Since the MConnection code panics, we need to recover here
  359. // and turn it into an error. Be careful not to alias err, so we can
  360. // update it from within this function. We should remove panics instead.
  361. defer func() {
  362. if r := recover(); r != nil {
  363. err = ErrRejected{
  364. conn: tcpConn,
  365. err: fmt.Errorf("recovered from panic: %v", r),
  366. isAuthFailure: true,
  367. }
  368. }
  369. }()
  370. err = tcpConn.SetDeadline(time.Now().Add(transport.handshakeTimeout))
  371. if err != nil {
  372. err = ErrRejected{
  373. conn: tcpConn,
  374. err: fmt.Errorf("secret conn failed: %v", err),
  375. isAuthFailure: true,
  376. }
  377. return
  378. }
  379. c = &mConnConnection{
  380. transport: transport,
  381. chReceive: make(chan mConnMessage),
  382. chError: make(chan error),
  383. chClose: make(chan struct{}),
  384. }
  385. c.secretConn, err = conn.MakeSecretConnection(tcpConn, transport.privKey)
  386. if err != nil {
  387. err = ErrRejected{
  388. conn: tcpConn,
  389. err: fmt.Errorf("secret conn failed: %v", err),
  390. isAuthFailure: true,
  391. }
  392. return
  393. }
  394. c.peerInfo, err = c.handshake()
  395. if err != nil {
  396. err = ErrRejected{
  397. conn: tcpConn,
  398. err: fmt.Errorf("handshake failed: %v", err),
  399. isAuthFailure: true,
  400. }
  401. return
  402. }
  403. // Validate node info.
  404. // FIXME: All of the ID verification code below should be moved to the
  405. // router once implemented.
  406. err = c.peerInfo.Validate()
  407. if err != nil {
  408. err = ErrRejected{
  409. conn: tcpConn,
  410. err: err,
  411. isNodeInfoInvalid: true,
  412. }
  413. return
  414. }
  415. // For outgoing conns, ensure connection key matches dialed key.
  416. if expectPeerID != "" {
  417. peerID := NodeIDFromPubKey(c.PubKey())
  418. if expectPeerID != peerID {
  419. err = ErrRejected{
  420. conn: tcpConn,
  421. id: peerID,
  422. err: fmt.Errorf(
  423. "conn.ID (%v) dialed ID (%v) mismatch",
  424. peerID,
  425. expectPeerID,
  426. ),
  427. isAuthFailure: true,
  428. }
  429. return
  430. }
  431. }
  432. // Reject self.
  433. if transport.nodeInfo.ID() == c.peerInfo.ID() {
  434. err = ErrRejected{
  435. addr: *NewNetAddress(c.peerInfo.ID(), c.secretConn.RemoteAddr()),
  436. conn: tcpConn,
  437. id: c.peerInfo.ID(),
  438. isSelf: true,
  439. }
  440. return
  441. }
  442. err = transport.nodeInfo.CompatibleWith(c.peerInfo)
  443. if err != nil {
  444. err = ErrRejected{
  445. conn: tcpConn,
  446. err: err,
  447. id: c.peerInfo.ID(),
  448. isIncompatible: true,
  449. }
  450. return
  451. }
  452. err = tcpConn.SetDeadline(time.Time{})
  453. if err != nil {
  454. err = ErrRejected{
  455. conn: tcpConn,
  456. err: fmt.Errorf("secret conn failed: %v", err),
  457. isAuthFailure: true,
  458. }
  459. return
  460. }
  461. // Set up the MConnection wrapper
  462. c.mConn = conn.NewMConnectionWithConfig(
  463. c.secretConn,
  464. transport.channelDescs,
  465. c.onReceive,
  466. c.onError,
  467. transport.mConnConfig,
  468. )
  469. // FIXME: Log format is set up for compatibility with existing peer code.
  470. c.logger = transport.logger.With("peer", c.RemoteEndpoint().NetAddress())
  471. c.mConn.SetLogger(c.logger)
  472. err = c.mConn.Start()
  473. return c, err
  474. }
  475. // handshake performs an MConn handshake, returning the peer's node info.
  476. func (c *mConnConnection) handshake() (NodeInfo, error) {
  477. var pbNodeInfo p2pproto.NodeInfo
  478. chErr := make(chan error, 2)
  479. go func() {
  480. _, err := protoio.NewDelimitedWriter(c.secretConn).WriteMsg(c.transport.nodeInfo.ToProto())
  481. chErr <- err
  482. }()
  483. go func() {
  484. _, err := protoio.NewDelimitedReader(c.secretConn, MaxNodeInfoSize()).ReadMsg(&pbNodeInfo)
  485. chErr <- err
  486. }()
  487. for i := 0; i < cap(chErr); i++ {
  488. if err := <-chErr; err != nil {
  489. return NodeInfo{}, err
  490. }
  491. }
  492. return NodeInfoFromProto(&pbNodeInfo)
  493. }
  494. // onReceive is a callback for MConnection received messages.
  495. func (c *mConnConnection) onReceive(channelID byte, payload []byte) {
  496. select {
  497. case c.chReceive <- mConnMessage{channelID: channelID, payload: payload}:
  498. case <-c.chClose:
  499. }
  500. }
  501. // onError is a callback for MConnection errors. The error is passed to
  502. // chError, which is only consumed by ReceiveMessage() for parity with
  503. // the old MConnection behavior.
  504. func (c *mConnConnection) onError(e interface{}) {
  505. err, ok := e.(error)
  506. if !ok {
  507. err = fmt.Errorf("%v", err)
  508. }
  509. select {
  510. case c.chError <- err:
  511. case <-c.chClose:
  512. }
  513. }
  514. // String displays connection information.
  515. // FIXME: This is here for backwards compatibility with existing code,
  516. // it should probably just return RemoteEndpoint().String(), if anything.
  517. func (c *mConnConnection) String() string {
  518. endpoint := c.RemoteEndpoint()
  519. return fmt.Sprintf("MConn{%v:%v}", endpoint.IP, endpoint.Port)
  520. }
  521. // SendMessage implements Connection.
  522. func (c *mConnConnection) SendMessage(channelID byte, msg []byte) (bool, error) {
  523. // We don't check chError here, to preserve old MConnection behavior.
  524. select {
  525. case <-c.chClose:
  526. return false, io.EOF
  527. default:
  528. return c.mConn.Send(channelID, msg), nil
  529. }
  530. }
  531. // TrySendMessage implements Connection.
  532. func (c *mConnConnection) TrySendMessage(channelID byte, msg []byte) (bool, error) {
  533. // We don't check chError here, to preserve old MConnection behavior.
  534. select {
  535. case <-c.chClose:
  536. return false, io.EOF
  537. default:
  538. return c.mConn.TrySend(channelID, msg), nil
  539. }
  540. }
  541. // ReceiveMessage implements Connection.
  542. func (c *mConnConnection) ReceiveMessage() (byte, []byte, error) {
  543. select {
  544. case err := <-c.chError:
  545. return 0, nil, err
  546. case <-c.chClose:
  547. return 0, nil, io.EOF
  548. case msg := <-c.chReceive:
  549. return msg.channelID, msg.payload, nil
  550. }
  551. }
  552. // NodeInfo implements Connection.
  553. func (c *mConnConnection) NodeInfo() NodeInfo {
  554. return c.peerInfo
  555. }
  556. // PubKey implements Connection.
  557. func (c *mConnConnection) PubKey() crypto.PubKey {
  558. return c.secretConn.RemotePubKey()
  559. }
  560. // LocalEndpoint implements Connection.
  561. func (c *mConnConnection) LocalEndpoint() Endpoint {
  562. // FIXME: For compatibility with existing P2P tests we need to
  563. // handle non-TCP connections. This should be removed.
  564. endpoint := Endpoint{
  565. Protocol: MConnProtocol,
  566. PeerID: c.transport.nodeInfo.ID(),
  567. }
  568. if addr, ok := c.secretConn.LocalAddr().(*net.TCPAddr); ok {
  569. endpoint.IP = addr.IP
  570. endpoint.Port = uint16(addr.Port)
  571. }
  572. return endpoint
  573. }
  574. // RemoteEndpoint implements Connection.
  575. func (c *mConnConnection) RemoteEndpoint() Endpoint {
  576. // FIXME: For compatibility with existing P2P tests we need to
  577. // handle non-TCP connections. This should be removed.
  578. endpoint := Endpoint{
  579. Protocol: MConnProtocol,
  580. PeerID: c.peerInfo.ID(),
  581. }
  582. if addr, ok := c.secretConn.RemoteAddr().(*net.TCPAddr); ok {
  583. endpoint.IP = addr.IP
  584. endpoint.Port = uint16(addr.Port)
  585. }
  586. return endpoint
  587. }
  588. // Status implements Connection.
  589. func (c *mConnConnection) Status() conn.ConnectionStatus {
  590. return c.mConn.Status()
  591. }
  592. // Close implements Connection.
  593. func (c *mConnConnection) Close() error {
  594. c.transport.conns.RemoveAddr(c.secretConn.RemoteAddr())
  595. var err error
  596. c.closeOnce.Do(func() {
  597. err = c.mConn.Stop()
  598. close(c.chClose)
  599. })
  600. return err
  601. }
  602. // FlushClose implements Connection.
  603. func (c *mConnConnection) FlushClose() error {
  604. c.transport.conns.RemoveAddr(c.secretConn.RemoteAddr())
  605. c.closeOnce.Do(func() {
  606. c.mConn.FlushStop()
  607. close(c.chClose)
  608. })
  609. return nil
  610. }