Skip to content

Commit

Permalink
webrtcprivate: add transport integrationt tests
Browse files Browse the repository at this point in the history
  • Loading branch information
sukunrt committed Oct 12, 2023
1 parent 44ff5db commit 7a57ad9
Show file tree
Hide file tree
Showing 6 changed files with 531 additions and 50 deletions.
241 changes: 212 additions & 29 deletions p2p/test/transport/gating_test.go
Expand Up @@ -6,6 +6,7 @@ import (
"testing"
"time"

"github.com/libp2p/go-libp2p/core/control"
"github.com/libp2p/go-libp2p/core/network"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-libp2p/core/protocol"
Expand Down Expand Up @@ -83,9 +84,6 @@ func TestInterceptSecuredOutgoing(t *testing.T) {
}
for _, tc := range transportsToTest {
t.Run(tc.Name, func(t *testing.T) {
if strings.Contains(tc.Name, "WebRTCPrivate") {
t.Skip("webrtcprivate needs different handling because of the relay required for connection setup")
}

ctrl := gomock.NewController(t)
defer ctrl.Finish()
Expand All @@ -99,14 +97,42 @@ func TestInterceptSecuredOutgoing(t *testing.T) {

ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
gomock.InOrder(
connGater.EXPECT().InterceptPeerDial(h2.ID()).Return(true),
connGater.EXPECT().InterceptAddrDial(h2.ID(), gomock.Any()).Return(true),
connGater.EXPECT().InterceptSecured(network.DirOutbound, h2.ID(), gomock.Any()).Do(func(_ network.Direction, _ peer.ID, addrs network.ConnMultiaddrs) {
// remove the certhash component from WebTransport and WebRTC addresses
require.Equal(t, stripCertHash(h2.Addrs()[0]).String(), addrs.RemoteMultiaddr().String())
}),
)
if strings.Contains(tc.Name, "WebRTCPrivate") {
gomock.InOrder(
connGater.EXPECT().InterceptPeerDial(h2.ID()).Return(true),
connGater.EXPECT().InterceptAddrDial(h2.ID(), gomock.Any()).Return(true),

// Calls for circuit-v2 conn setup
connGater.EXPECT().InterceptPeerDial(h2.ID()).Return(true),
connGater.EXPECT().InterceptAddrDial(h2.ID(), gomock.Any()).Return(true),
connGater.EXPECT().InterceptAddrDial(gomock.Any(), gomock.Any()).Return(true), // two addresses for the peer

// Calls for connection to relay node
connGater.EXPECT().InterceptPeerDial(gomock.Any()).Return(true),
connGater.EXPECT().InterceptAddrDial(gomock.Any(), gomock.Any()).Return(true),
connGater.EXPECT().InterceptSecured(network.DirOutbound, gomock.Any(), gomock.Any()).Return(true),
connGater.EXPECT().InterceptUpgraded(gomock.Any()).AnyTimes().Return(true, control.DisconnectReason(0)),

// circuit-v2 setup complete
connGater.EXPECT().InterceptSecured(network.DirOutbound, gomock.Any(), gomock.Any()).Return(true),
connGater.EXPECT().InterceptUpgraded(gomock.Any()).AnyTimes().Return(true, control.DisconnectReason(0)),

// webrtcprivate setup complete
// TODO: fix addresses on both sides of the /webrtc connection
connGater.EXPECT().InterceptSecured(network.DirOutbound, h2.ID(), gomock.Any()),
)
// force a direct connection
ctx = network.WithForceDirectDial(ctx, "integration test /webrtc")
} else {
gomock.InOrder(
connGater.EXPECT().InterceptPeerDial(h2.ID()).Return(true),
connGater.EXPECT().InterceptAddrDial(h2.ID(), gomock.Any()).Return(true),
connGater.EXPECT().InterceptSecured(network.DirOutbound, h2.ID(), gomock.Any()).Do(func(_ network.Direction, _ peer.ID, addrs network.ConnMultiaddrs) {
// remove the certhash component from WebTransport and WebRTC addresses
require.Equal(t, stripCertHash(h2.Addrs()[0]).String(), addrs.RemoteMultiaddr().String())
}),
)
}

err := h1.Connect(ctx, peer.AddrInfo{ID: h2.ID(), Addrs: h2.Addrs()})
require.Error(t, err)
Expand All @@ -121,9 +147,6 @@ func TestInterceptUpgradedOutgoing(t *testing.T) {
}
for _, tc := range transportsToTest {
t.Run(tc.Name, func(t *testing.T) {
if strings.Contains(tc.Name, "WebRTCPrivate") {
t.Skip("webrtc private to private needs special handling because the listener makes a dial to the relay")
}
ctrl := gomock.NewController(t)
defer ctrl.Finish()
connGater := NewMockConnectionGater(ctrl)
Expand All @@ -137,16 +160,48 @@ func TestInterceptUpgradedOutgoing(t *testing.T) {

ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
gomock.InOrder(
connGater.EXPECT().InterceptPeerDial(h2.ID()).Return(true),
connGater.EXPECT().InterceptAddrDial(h2.ID(), gomock.Any()).Return(true),
connGater.EXPECT().InterceptSecured(network.DirOutbound, h2.ID(), gomock.Any()).Return(true),
connGater.EXPECT().InterceptUpgraded(gomock.Any()).Do(func(c network.Conn) {
// remove the certhash component from WebTransport addresses
require.Equal(t, stripCertHash(h2.Addrs()[0]), c.RemoteMultiaddr())
require.Equal(t, h1.ID(), c.LocalPeer())
require.Equal(t, h2.ID(), c.RemotePeer())
}))
if strings.Contains(tc.Name, "WebRTCPrivate") {
gomock.InOrder(
connGater.EXPECT().InterceptPeerDial(h2.ID()).Return(true),
connGater.EXPECT().InterceptAddrDial(h2.ID(), gomock.Any()).Return(true),

// Calls for circuit-v2 conn setup
connGater.EXPECT().InterceptPeerDial(h2.ID()).Return(true),
connGater.EXPECT().InterceptAddrDial(h2.ID(), gomock.Any()).Return(true),
connGater.EXPECT().InterceptAddrDial(gomock.Any(), gomock.Any()).Return(true), // two addresses for the peer

// Calls for connection to relay node
connGater.EXPECT().InterceptPeerDial(gomock.Any()).Return(true),
connGater.EXPECT().InterceptAddrDial(gomock.Any(), gomock.Any()).Return(true),
connGater.EXPECT().InterceptSecured(network.DirOutbound, gomock.Any(), gomock.Any()).Return(true),
connGater.EXPECT().InterceptUpgraded(gomock.Any()).Return(true, control.DisconnectReason(0)),

// circuit-v2 setup complete
connGater.EXPECT().InterceptSecured(network.DirOutbound, gomock.Any(), gomock.Any()).Return(true),
connGater.EXPECT().InterceptUpgraded(gomock.Any()).Return(true, control.DisconnectReason(0)),

// webrtcprivate setup complete
// TODO: fix addresses on both sides of the /webrtc connection
connGater.EXPECT().InterceptSecured(network.DirOutbound, h2.ID(), gomock.Any()).Return(true),
connGater.EXPECT().InterceptUpgraded(gomock.Any()).Do(func(c network.Conn) {
require.Equal(t, h1.ID(), c.LocalPeer())
require.Equal(t, h2.ID(), c.RemotePeer())
}),
)
// force a direct connection
ctx = network.WithForceDirectDial(ctx, "integration test /webrtc")
} else {
gomock.InOrder(
connGater.EXPECT().InterceptPeerDial(h2.ID()).Return(true),
connGater.EXPECT().InterceptAddrDial(h2.ID(), gomock.Any()).Return(true),
connGater.EXPECT().InterceptSecured(network.DirOutbound, h2.ID(), gomock.Any()).Return(true),
connGater.EXPECT().InterceptUpgraded(gomock.Any()).Do(func(c network.Conn) {
// remove the certhash component from WebTransport addresses
require.Equal(t, stripCertHash(h2.Addrs()[0]), c.RemoteMultiaddr())
require.Equal(t, h1.ID(), c.LocalPeer())
require.Equal(t, h2.ID(), c.RemotePeer())
}))
}
err := h1.Connect(ctx, peer.AddrInfo{ID: h2.ID(), Addrs: h2.Addrs()})
require.Error(t, err)
require.NotErrorIs(t, err, context.DeadlineExceeded)
Expand All @@ -161,7 +216,8 @@ func TestInterceptAccept(t *testing.T) {
for _, tc := range transportsToTest {
t.Run(tc.Name, func(t *testing.T) {
if strings.Contains(tc.Name, "WebRTCPrivate") {
t.Skip("webrtc private to private needs special handling because the listener makes a dial to the relay")
testInterceptAcceptIncomingWebRTCPrivate(t, tc)
return
}
ctrl := gomock.NewController(t)
defer ctrl.Finish()
Expand Down Expand Up @@ -202,14 +258,53 @@ func TestInterceptAccept(t *testing.T) {
}
}

func testInterceptAcceptIncomingWebRTCPrivate(t *testing.T, tc TransportTestCase) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
connGater := NewMockConnectionGater(ctrl)

// Relay reservation calls
connGater.EXPECT().InterceptPeerDial(gomock.Any()).Return(true)
connGater.EXPECT().InterceptAddrDial(gomock.Any(), gomock.Any()).Return(true)
connGater.EXPECT().InterceptSecured(gomock.Any(), gomock.Any(), gomock.Any()).Return(true)
connGater.EXPECT().InterceptUpgraded(gomock.Any()).Return(true, control.DisconnectReason(0))

h1 := tc.HostGenerator(t, TransportTestCaseOpts{NoListen: true})
h2 := tc.HostGenerator(t, TransportTestCaseOpts{ConnGater: connGater})
defer h1.Close()
defer h2.Close()
require.Len(t, h2.Addrs(), 1)

ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
// relayed connection for incoming stream
connGater.EXPECT().InterceptAccept(gomock.Any()).Return(true)
connGater.EXPECT().InterceptSecured(gomock.Any(), gomock.Any(), gomock.Any()).Return(true)
connGater.EXPECT().InterceptUpgraded(gomock.Any()).Return(true, control.DisconnectReason(0))
// webrtc connection accept
// TODO: Fix webrtc addresses on both sides.
connGater.EXPECT().InterceptAccept(gomock.Any())

// The basic host dials the first connection.
h1.Peerstore().AddAddrs(h2.ID(), h2.Addrs(), time.Hour)
_, err := h1.NewStream(ctx, h2.ID(), protocol.TestingID)
require.Error(t, err)
if _, err := h2.Addrs()[0].ValueForProtocol(ma.P_WEBRTC_DIRECT); err != nil {
// WebRTC rejects connection attempt before an error can be sent to the client.
// This means that the connection attempt will time out.
require.NotErrorIs(t, err, context.DeadlineExceeded)
}
}

func TestInterceptSecuredIncoming(t *testing.T) {
if race.WithRace() {
t.Skip("The upgrader spawns a new Go routine, which leads to race conditions when using GoMock.")
}
for _, tc := range transportsToTest {
t.Run(tc.Name, func(t *testing.T) {
if strings.Contains(tc.Name, "WebRTCPrivate") {
t.Skip("webrtc private to private needs special handling because the listener makes a dial to the relay")
testInterceptSecuredIncomingWebRTCPrivate(t, tc)
return
}

ctrl := gomock.NewController(t)
Expand Down Expand Up @@ -239,14 +334,59 @@ func TestInterceptSecuredIncoming(t *testing.T) {
}
}

func testInterceptSecuredIncomingWebRTCPrivate(t *testing.T, tc TransportTestCase) {
t.Helper()

ctrl := gomock.NewController(t)
defer ctrl.Finish()
connGater := NewMockConnectionGater(ctrl)
gomock.InOrder(
// Dial to relay node for circuit reservation
connGater.EXPECT().InterceptPeerDial(gomock.Any()).Return(true),
connGater.EXPECT().InterceptAddrDial(gomock.Any(), gomock.Any()).Return(true),
connGater.EXPECT().InterceptSecured(gomock.Any(), gomock.Any(), gomock.Any()).Return(true),
connGater.EXPECT().InterceptUpgraded(gomock.Any()).Return(true, control.DisconnectReason(0)),
// Incoming relay connection for signaling stream
connGater.EXPECT().InterceptAccept(gomock.Any()).Return(true),
connGater.EXPECT().InterceptSecured(gomock.Any(), gomock.Any(), gomock.Any()).Return(true),
connGater.EXPECT().InterceptUpgraded(gomock.Any()).Return(true, control.DisconnectReason(0)),
)
h1 := tc.HostGenerator(t, TransportTestCaseOpts{NoListen: true})
h2 := tc.HostGenerator(t, TransportTestCaseOpts{ConnGater: connGater})
defer h1.Close()
defer h2.Close()

require.Len(t, h2.Addrs(), 1)

ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
ctx = network.WithForceDirectDial(ctx, "transport integration test /webrtc")
gomock.InOrder(
connGater.EXPECT().InterceptAccept(gomock.Any()).Return(true),
connGater.EXPECT().InterceptSecured(network.DirInbound, h1.ID(), gomock.Any()),
)

h1.Peerstore().AddAddrs(h2.ID(), h2.Addrs(), time.Hour)
_, err := h1.NewStream(ctx, h2.ID(), protocol.TestingID)
require.Error(t, err)

// Do not check that error is Context Deadline Exceeded
// WebRTCPrivate connection establishment is considered complete when the DTLS handshake finishes.
// At this point no SCTP association is established. Closing the connection on the listener side,
// immediately after accepting, closes the listener side of the connection before the SCTP association
// is established. Pion doesn't handle this case nicely and webrtc.PeerConnection on the dialer will
// only be considered closed once the ICE transport times out.
}

func TestInterceptUpgradedIncoming(t *testing.T) {
if race.WithRace() {
t.Skip("The upgrader spawns a new Go routine, which leads to race conditions when using GoMock.")
}
for _, tc := range transportsToTest {
t.Run(tc.Name, func(t *testing.T) {
if strings.Contains(tc.Name, "WebRTCPrivate") {
t.Skip("webrtc private to private needs special handling because the listener makes a dial to the relay")
testInterceptUpgradeIncomingWebRTCPrivate(t, tc)
return
}
ctrl := gomock.NewController(t)
defer ctrl.Finish()
Expand All @@ -258,8 +398,6 @@ func TestInterceptUpgradedIncoming(t *testing.T) {
defer h2.Close()
require.Len(t, h2.Addrs(), 1)

ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
gomock.InOrder(
connGater.EXPECT().InterceptAccept(gomock.Any()).Return(true),
connGater.EXPECT().InterceptSecured(network.DirInbound, h1.ID(), gomock.Any()).Return(true),
Expand All @@ -270,10 +408,55 @@ func TestInterceptUpgradedIncoming(t *testing.T) {
require.Equal(t, h2.ID(), c.LocalPeer())
}),
)
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
h1.Peerstore().AddAddrs(h2.ID(), h2.Addrs(), time.Hour)
_, err := h1.NewStream(ctx, h2.ID(), protocol.TestingID)
require.Error(t, err)
require.NotErrorIs(t, err, context.DeadlineExceeded)
})
}
}

func testInterceptUpgradeIncomingWebRTCPrivate(t *testing.T, tc TransportTestCase) {
t.Helper()
ctrl := gomock.NewController(t)
defer ctrl.Finish()
connGater := NewMockConnectionGater(ctrl)

gomock.InOrder(
// Dial to relay node for circuit reservation
connGater.EXPECT().InterceptPeerDial(gomock.Any()).Return(true),
connGater.EXPECT().InterceptAddrDial(gomock.Any(), gomock.Any()).Return(true),
connGater.EXPECT().InterceptSecured(gomock.Any(), gomock.Any(), gomock.Any()).Return(true),
connGater.EXPECT().InterceptUpgraded(gomock.Any()).Return(true, control.DisconnectReason(0)),
// Incoming relay connection for signaling stream
connGater.EXPECT().InterceptAccept(gomock.Any()).Return(true),
connGater.EXPECT().InterceptSecured(gomock.Any(), gomock.Any(), gomock.Any()).Return(true),
connGater.EXPECT().InterceptUpgraded(gomock.Any()).Return(true, control.DisconnectReason(0)),
)

h1 := tc.HostGenerator(t, TransportTestCaseOpts{NoListen: true})
h2 := tc.HostGenerator(t, TransportTestCaseOpts{ConnGater: connGater})
defer h1.Close()
defer h2.Close()

require.Len(t, h2.Addrs(), 1)

gomock.InOrder(
connGater.EXPECT().InterceptAccept(gomock.Any()).Return(true),
connGater.EXPECT().InterceptSecured(network.DirInbound, h1.ID(), gomock.Any()).Return(true),
connGater.EXPECT().InterceptUpgraded(gomock.Any()).Do(func(c network.Conn) {
require.Equal(t, h1.ID(), c.RemotePeer())
require.Equal(t, h2.ID(), c.LocalPeer())
}),
)

ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
ctx = network.WithForceDirectDial(ctx, "transport integration test /webrtc")

h1.Peerstore().AddAddrs(h2.ID(), h2.Addrs(), time.Hour)
_, err := h1.NewStream(ctx, h2.ID(), protocol.TestingID)
require.Error(t, err)
}

0 comments on commit 7a57ad9

Please sign in to comment.