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.

680 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: 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, nil
  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", fmt.Sprintf("%v:%v", endpoint.IP, endpoint.Port))
  225. if err != nil {
  226. return nil, err
  227. }
  228. err = m.filterTCPConn(tcpConn)
  229. if err != nil {
  230. if err := tcpConn.Close(); err != nil {
  231. m.logger.Debug("failed to close TCP connection", "err", err)
  232. }
  233. return nil, err
  234. }
  235. conn, err := newMConnConnection(m, tcpConn, endpoint.PeerID)
  236. if err != nil {
  237. m.conns.Remove(tcpConn)
  238. if err := tcpConn.Close(); err != nil {
  239. m.logger.Debug("failed to close TCP connection", "err", err)
  240. }
  241. return nil, err
  242. }
  243. return conn, nil
  244. }
  245. // Endpoints implements Transport.
  246. func (m *MConnTransport) Endpoints() []Endpoint {
  247. if m.listener == nil {
  248. return []Endpoint{}
  249. }
  250. addr := m.listener.Addr().(*net.TCPAddr)
  251. return []Endpoint{{
  252. Protocol: MConnProtocol,
  253. PeerID: m.nodeInfo.ID(),
  254. IP: addr.IP,
  255. Port: uint16(addr.Port),
  256. }}
  257. }
  258. // Close implements Transport.
  259. func (m *MConnTransport) Close() error {
  260. var err error
  261. m.closeOnce.Do(func() {
  262. // We have to close chClose first, so that accept() will detect
  263. // the closure and not propagate the error.
  264. close(m.chClose)
  265. if m.listener != nil {
  266. err = m.listener.Close()
  267. }
  268. })
  269. return err
  270. }
  271. // filterTCPConn filters a TCP connection, rejecting it if this function errors.
  272. func (m *MConnTransport) filterTCPConn(tcpConn net.Conn) error {
  273. if m.conns.Has(tcpConn) {
  274. return ErrRejected{conn: tcpConn, isDuplicate: true}
  275. }
  276. host, _, err := net.SplitHostPort(tcpConn.RemoteAddr().String())
  277. if err != nil {
  278. return err
  279. }
  280. ip := net.ParseIP(host)
  281. if ip == nil {
  282. return fmt.Errorf("connection address has invalid IP address %q", host)
  283. }
  284. // Apply filter callbacks.
  285. chErr := make(chan error, len(m.connFilters))
  286. for _, connFilter := range m.connFilters {
  287. go func(connFilter ConnFilterFunc) {
  288. chErr <- connFilter(m.conns, tcpConn, []net.IP{ip})
  289. }(connFilter)
  290. }
  291. for i := 0; i < cap(chErr); i++ {
  292. select {
  293. case err := <-chErr:
  294. if err != nil {
  295. return ErrRejected{conn: tcpConn, err: err, isFiltered: true}
  296. }
  297. case <-time.After(m.filterTimeout):
  298. return ErrFilterTimeout{}
  299. }
  300. }
  301. // FIXME: Doesn't really make sense to set this here, but we preserve the
  302. // behavior from the previous P2P transport implementation. This should
  303. // be moved to the router.
  304. m.conns.Set(tcpConn, []net.IP{ip})
  305. return nil
  306. }
  307. // normalizeEndpoint normalizes and validates an endpoint.
  308. func (m *MConnTransport) normalizeEndpoint(endpoint *Endpoint) error {
  309. if endpoint == nil {
  310. return errors.New("nil endpoint")
  311. }
  312. if err := endpoint.Validate(); err != nil {
  313. return err
  314. }
  315. if endpoint.Protocol == "" {
  316. endpoint.Protocol = MConnProtocol
  317. }
  318. if endpoint.Protocol != MConnProtocol {
  319. return fmt.Errorf("unsupported protocol %q", endpoint.Protocol)
  320. }
  321. if len(endpoint.IP) == 0 {
  322. return errors.New("endpoint must have an IP address")
  323. }
  324. if endpoint.Path != "" {
  325. return fmt.Errorf("endpoint cannot have path (got %q)", endpoint.Path)
  326. }
  327. if endpoint.Port == 0 {
  328. endpoint.Port = 26657
  329. }
  330. return nil
  331. }
  332. // mConnConnection implements Connection for MConnTransport. It takes a base TCP
  333. // connection and upgrades it to MConnection over an encrypted SecretConnection.
  334. type mConnConnection struct {
  335. logger log.Logger
  336. transport *MConnTransport
  337. secretConn *conn.SecretConnection
  338. mConn *conn.MConnection
  339. peerInfo NodeInfo
  340. closeOnce sync.Once
  341. chReceive chan mConnMessage
  342. chError chan error
  343. chClose chan struct{}
  344. }
  345. // mConnMessage passes MConnection messages through internal channels.
  346. type mConnMessage struct {
  347. channelID byte
  348. payload []byte
  349. }
  350. // newMConnConnection creates a new mConnConnection by handshaking
  351. // with a peer.
  352. func newMConnConnection(
  353. transport *MConnTransport,
  354. tcpConn net.Conn,
  355. expectPeerID ID,
  356. ) (c *mConnConnection, err error) {
  357. // FIXME: Since the MConnection code panics, we need to recover here
  358. // and turn it into an error. Be careful not to alias err, so we can
  359. // update it from within this function. We should remove panics instead.
  360. defer func() {
  361. if r := recover(); r != nil {
  362. err = ErrRejected{
  363. conn: tcpConn,
  364. err: fmt.Errorf("recovered from panic: %v", r),
  365. isAuthFailure: true,
  366. }
  367. }
  368. }()
  369. err = tcpConn.SetDeadline(time.Now().Add(transport.handshakeTimeout))
  370. if err != nil {
  371. err = ErrRejected{
  372. conn: tcpConn,
  373. err: fmt.Errorf("secret conn failed: %v", err),
  374. isAuthFailure: true,
  375. }
  376. return
  377. }
  378. c = &mConnConnection{
  379. transport: transport,
  380. chReceive: make(chan mConnMessage),
  381. chError: make(chan error),
  382. chClose: make(chan struct{}),
  383. }
  384. c.secretConn, err = conn.MakeSecretConnection(tcpConn, transport.privKey)
  385. if err != nil {
  386. err = ErrRejected{
  387. conn: tcpConn,
  388. err: fmt.Errorf("secret conn failed: %v", err),
  389. isAuthFailure: true,
  390. }
  391. return
  392. }
  393. c.peerInfo, err = c.handshake()
  394. if err != nil {
  395. err = ErrRejected{
  396. conn: tcpConn,
  397. err: fmt.Errorf("handshake failed: %v", err),
  398. isAuthFailure: true,
  399. }
  400. return
  401. }
  402. // Validate node info.
  403. // FIXME: All of the ID verification code below should be moved to the
  404. // router once implemented.
  405. err = c.peerInfo.Validate()
  406. if err != nil {
  407. err = ErrRejected{
  408. conn: tcpConn,
  409. err: err,
  410. isNodeInfoInvalid: true,
  411. }
  412. return
  413. }
  414. // For outgoing conns, ensure connection key matches dialed key.
  415. if expectPeerID != "" {
  416. peerID := PubKeyToID(c.PubKey())
  417. if expectPeerID != peerID {
  418. err = ErrRejected{
  419. conn: tcpConn,
  420. id: peerID,
  421. err: fmt.Errorf(
  422. "conn.ID (%v) dialed ID (%v) mismatch",
  423. peerID,
  424. expectPeerID,
  425. ),
  426. isAuthFailure: true,
  427. }
  428. return
  429. }
  430. }
  431. // Reject self.
  432. if transport.nodeInfo.ID() == c.peerInfo.ID() {
  433. err = ErrRejected{
  434. addr: *NewNetAddress(c.peerInfo.ID(), c.secretConn.RemoteAddr()),
  435. conn: tcpConn,
  436. id: c.peerInfo.ID(),
  437. isSelf: true,
  438. }
  439. return
  440. }
  441. err = transport.nodeInfo.CompatibleWith(c.peerInfo)
  442. if err != nil {
  443. err = ErrRejected{
  444. conn: tcpConn,
  445. err: err,
  446. id: c.peerInfo.ID(),
  447. isIncompatible: true,
  448. }
  449. return
  450. }
  451. err = tcpConn.SetDeadline(time.Time{})
  452. if err != nil {
  453. err = ErrRejected{
  454. conn: tcpConn,
  455. err: fmt.Errorf("secret conn failed: %v", err),
  456. isAuthFailure: true,
  457. }
  458. return
  459. }
  460. // Set up the MConnection wrapper
  461. c.mConn = conn.NewMConnectionWithConfig(
  462. c.secretConn,
  463. transport.channelDescs,
  464. c.onReceive,
  465. c.onError,
  466. transport.mConnConfig,
  467. )
  468. // FIXME: Log format is set up for compatibility with existing peer code.
  469. c.logger = transport.logger.With("peer", c.RemoteEndpoint().NetAddress())
  470. c.mConn.SetLogger(c.logger)
  471. err = c.mConn.Start()
  472. return c, err
  473. }
  474. // handshake performs an MConn handshake, returning the peer's node info.
  475. func (c *mConnConnection) handshake() (NodeInfo, error) {
  476. var pbNodeInfo p2pproto.NodeInfo
  477. chErr := make(chan error, 2)
  478. go func() {
  479. _, err := protoio.NewDelimitedWriter(c.secretConn).WriteMsg(c.transport.nodeInfo.ToProto())
  480. chErr <- err
  481. }()
  482. go func() {
  483. chErr <- protoio.NewDelimitedReader(c.secretConn, MaxNodeInfoSize()).ReadMsg(&pbNodeInfo)
  484. }()
  485. for i := 0; i < cap(chErr); i++ {
  486. if err := <-chErr; err != nil {
  487. return NodeInfo{}, err
  488. }
  489. }
  490. return NodeInfoFromProto(&pbNodeInfo)
  491. }
  492. // onReceive is a callback for MConnection received messages.
  493. func (c *mConnConnection) onReceive(channelID byte, payload []byte) {
  494. select {
  495. case c.chReceive <- mConnMessage{channelID: channelID, payload: payload}:
  496. case <-c.chClose:
  497. }
  498. }
  499. // onError is a callback for MConnection errors. The error is passed to
  500. // chError, which is only consumed by ReceiveMessage() for parity with
  501. // the old MConnection behavior.
  502. func (c *mConnConnection) onError(e interface{}) {
  503. err, ok := e.(error)
  504. if !ok {
  505. err = fmt.Errorf("%v", err)
  506. }
  507. select {
  508. case c.chError <- err:
  509. case <-c.chClose:
  510. }
  511. }
  512. // String displays connection information.
  513. // FIXME: This is here for backwards compatibility with existing code,
  514. // it should probably just return RemoteEndpoint().String(), if anything.
  515. func (c *mConnConnection) String() string {
  516. endpoint := c.RemoteEndpoint()
  517. return fmt.Sprintf("MConn{%v:%v}", endpoint.IP, endpoint.Port)
  518. }
  519. // SendMessage implements Connection.
  520. func (c *mConnConnection) SendMessage(channelID byte, msg []byte) (bool, error) {
  521. // We don't check chError here, to preserve old MConnection behavior.
  522. select {
  523. case <-c.chClose:
  524. return false, io.EOF
  525. default:
  526. return c.mConn.Send(channelID, msg), nil
  527. }
  528. }
  529. // TrySendMessage implements Connection.
  530. func (c *mConnConnection) TrySendMessage(channelID byte, msg []byte) (bool, error) {
  531. // We don't check chError here, to preserve old MConnection behavior.
  532. select {
  533. case <-c.chClose:
  534. return false, io.EOF
  535. default:
  536. return c.mConn.TrySend(channelID, msg), nil
  537. }
  538. }
  539. // ReceiveMessage implements Connection.
  540. func (c *mConnConnection) ReceiveMessage() (byte, []byte, error) {
  541. select {
  542. case err := <-c.chError:
  543. return 0, nil, err
  544. case <-c.chClose:
  545. return 0, nil, io.EOF
  546. case msg := <-c.chReceive:
  547. return msg.channelID, msg.payload, nil
  548. }
  549. }
  550. // NodeInfo implements Connection.
  551. func (c *mConnConnection) NodeInfo() NodeInfo {
  552. return c.peerInfo
  553. }
  554. // PubKey implements Connection.
  555. func (c *mConnConnection) PubKey() crypto.PubKey {
  556. return c.secretConn.RemotePubKey()
  557. }
  558. // LocalEndpoint implements Connection.
  559. func (c *mConnConnection) LocalEndpoint() Endpoint {
  560. // FIXME: For compatibility with existing P2P tests we need to
  561. // handle non-TCP connections. This should be removed.
  562. endpoint := Endpoint{
  563. Protocol: MConnProtocol,
  564. PeerID: c.transport.nodeInfo.ID(),
  565. }
  566. if addr, ok := c.secretConn.LocalAddr().(*net.TCPAddr); ok {
  567. endpoint.IP = addr.IP
  568. endpoint.Port = uint16(addr.Port)
  569. }
  570. return endpoint
  571. }
  572. // RemoteEndpoint implements Connection.
  573. func (c *mConnConnection) RemoteEndpoint() Endpoint {
  574. // FIXME: For compatibility with existing P2P tests we need to
  575. // handle non-TCP connections. This should be removed.
  576. endpoint := Endpoint{
  577. Protocol: MConnProtocol,
  578. PeerID: c.peerInfo.ID(),
  579. }
  580. if addr, ok := c.secretConn.RemoteAddr().(*net.TCPAddr); ok {
  581. endpoint.IP = addr.IP
  582. endpoint.Port = uint16(addr.Port)
  583. }
  584. return endpoint
  585. }
  586. // Status implements Connection.
  587. func (c *mConnConnection) Status() conn.ConnectionStatus {
  588. return c.mConn.Status()
  589. }
  590. // Close implements Connection.
  591. func (c *mConnConnection) Close() error {
  592. c.transport.conns.RemoveAddr(c.secretConn.RemoteAddr())
  593. var err error
  594. c.closeOnce.Do(func() {
  595. err = c.mConn.Stop()
  596. close(c.chClose)
  597. })
  598. return err
  599. }
  600. // FlushClose implements Connection.
  601. func (c *mConnConnection) FlushClose() error {
  602. c.transport.conns.RemoveAddr(c.secretConn.RemoteAddr())
  603. c.closeOnce.Do(func() {
  604. c.mConn.FlushStop()
  605. close(c.chClose)
  606. })
  607. return nil
  608. }