package p2p_test import ( "context" "io" "net" "testing" "time" "github.com/fortytw2/leaktest" "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/internal/p2p" "github.com/tendermint/tendermint/internal/p2p/conn" "github.com/tendermint/tendermint/libs/log" ) // Transports are mainly tested by common tests in transport_test.go, we // register a transport factory here to get included in those tests. func init() { testTransports["mconn"] = func(t *testing.T) p2p.Transport { transport := p2p.NewMConnTransport( log.TestingLogger(), conn.DefaultMConnConfig(), []*p2p.ChannelDescriptor{{ID: chID, Priority: 1}}, p2p.MConnTransportOptions{}, ) err := transport.Listen(p2p.Endpoint{ Protocol: p2p.MConnProtocol, IP: net.IPv4(127, 0, 0, 1), Port: 0, // assign a random port }) require.NoError(t, err) t.Cleanup(func() { require.NoError(t, transport.Close()) }) return transport } } func TestMConnTransport_AcceptBeforeListen(t *testing.T) { transport := p2p.NewMConnTransport( log.TestingLogger(), conn.DefaultMConnConfig(), []*p2p.ChannelDescriptor{{ID: chID, Priority: 1}}, p2p.MConnTransportOptions{ MaxAcceptedConnections: 2, }, ) t.Cleanup(func() { _ = transport.Close() }) ctx, cancel := context.WithCancel(context.Background()) defer cancel() _, err := transport.Accept(ctx) require.Error(t, err) require.NotEqual(t, io.EOF, err) // io.EOF should be returned after Close() } func TestMConnTransport_AcceptMaxAcceptedConnections(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() transport := p2p.NewMConnTransport( log.TestingLogger(), conn.DefaultMConnConfig(), []*p2p.ChannelDescriptor{{ID: chID, Priority: 1}}, p2p.MConnTransportOptions{ MaxAcceptedConnections: 2, }, ) t.Cleanup(func() { _ = transport.Close() }) err := transport.Listen(p2p.Endpoint{ Protocol: p2p.MConnProtocol, IP: net.IPv4(127, 0, 0, 1), }) require.NoError(t, err) require.NotEmpty(t, transport.Endpoints()) endpoint := transport.Endpoints()[0] // Start a goroutine to just accept any connections. acceptCh := make(chan p2p.Connection, 10) go func() { for { conn, err := transport.Accept(ctx) if err != nil { return } acceptCh <- conn } }() // The first two connections should be accepted just fine. dial1, err := transport.Dial(ctx, endpoint) require.NoError(t, err) defer dial1.Close() accept1 := <-acceptCh defer accept1.Close() require.Equal(t, dial1.LocalEndpoint(), accept1.RemoteEndpoint()) dial2, err := transport.Dial(ctx, endpoint) require.NoError(t, err) defer dial2.Close() accept2 := <-acceptCh defer accept2.Close() require.Equal(t, dial2.LocalEndpoint(), accept2.RemoteEndpoint()) // The third connection will be dialed successfully, but the accept should // not go through. dial3, err := transport.Dial(ctx, endpoint) require.NoError(t, err) defer dial3.Close() select { case <-acceptCh: require.Fail(t, "unexpected accept") case <-time.After(time.Second): } // However, once either of the other connections are closed, the accept // goes through. require.NoError(t, accept1.Close()) accept3 := <-acceptCh defer accept3.Close() require.Equal(t, dial3.LocalEndpoint(), accept3.RemoteEndpoint()) } func TestMConnTransport_Listen(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() testcases := []struct { endpoint p2p.Endpoint ok bool }{ // Valid v4 and v6 addresses, with mconn and tcp protocols. {p2p.Endpoint{Protocol: p2p.MConnProtocol, IP: net.IPv4zero}, true}, {p2p.Endpoint{Protocol: p2p.MConnProtocol, IP: net.IPv4(127, 0, 0, 1)}, true}, {p2p.Endpoint{Protocol: p2p.MConnProtocol, IP: net.IPv6zero}, true}, {p2p.Endpoint{Protocol: p2p.MConnProtocol, IP: net.IPv6loopback}, true}, {p2p.Endpoint{Protocol: p2p.TCPProtocol, IP: net.IPv4zero}, true}, // Invalid endpoints. {p2p.Endpoint{}, false}, {p2p.Endpoint{Protocol: p2p.MConnProtocol, Path: "foo"}, false}, {p2p.Endpoint{Protocol: p2p.MConnProtocol, IP: net.IPv4zero, Path: "foo"}, false}, } for _, tc := range testcases { tc := tc t.Run(tc.endpoint.String(), func(t *testing.T) { t.Cleanup(leaktest.Check(t)) transport := p2p.NewMConnTransport( log.TestingLogger(), conn.DefaultMConnConfig(), []*p2p.ChannelDescriptor{{ID: chID, Priority: 1}}, p2p.MConnTransportOptions{}, ) // Transport should not listen on any endpoints yet. require.Empty(t, transport.Endpoints()) // Start listening, and check any expected errors. err := transport.Listen(tc.endpoint) if !tc.ok { require.Error(t, err) return } require.NoError(t, err) // Check the endpoint. endpoints := transport.Endpoints() require.Len(t, endpoints, 1) endpoint := endpoints[0] require.Equal(t, p2p.MConnProtocol, endpoint.Protocol) if tc.endpoint.IP.IsUnspecified() { require.True(t, endpoint.IP.IsUnspecified(), "expected unspecified IP, got %v", endpoint.IP) } else { require.True(t, tc.endpoint.IP.Equal(endpoint.IP), "expected %v, got %v", tc.endpoint.IP, endpoint.IP) } require.NotZero(t, endpoint.Port) require.Empty(t, endpoint.Path) dialedChan := make(chan struct{}) var peerConn p2p.Connection go func() { // Dialing the endpoint should work. var err error ctx, cancel := context.WithCancel(context.Background()) defer cancel() peerConn, err = transport.Dial(ctx, endpoint) require.NoError(t, err) close(dialedChan) }() conn, err := transport.Accept(ctx) require.NoError(t, err) _ = conn.Close() <-dialedChan // closing the connection should not error require.NoError(t, peerConn.Close()) // try to read from the connection should error _, _, err = peerConn.ReceiveMessage(ctx) require.Error(t, err) // Trying to listen again should error. err = transport.Listen(tc.endpoint) require.Error(t, err) // close the transport _ = transport.Close() // Dialing the closed endpoint should error _, err = transport.Dial(ctx, endpoint) require.Error(t, err) }) } }