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.
 
 
 
 
 
 

227 lines
6.0 KiB

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() { _ = 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)
})
}
}