Skip to content

Commit

Permalink
quic: add DisableReuseport option (#1476)
Browse files Browse the repository at this point in the history
* feat: add `DisableReuseport` option

Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com>

* fix: `TestStatelessReset/reuseport_off` test

close listener underlying connection when reuseport is disabled and the close
method called

Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com>

* fix: skip `DisableReuseport` option on `TestHolePunching`

Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com>

Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com>
  • Loading branch information
gfanton authored Aug 19, 2022
1 parent ceece5a commit 0f4a969
Show file tree
Hide file tree
Showing 5 changed files with 216 additions and 48 deletions.
11 changes: 10 additions & 1 deletion p2p/transport/quic/conn.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package libp2pquic

import (
"context"
"net"

ic "github.com/libp2p/go-libp2p/core/crypto"
"github.com/libp2p/go-libp2p/core/network"
Expand All @@ -12,9 +13,17 @@ import (
ma "github.com/multiformats/go-multiaddr"
)

type pConn interface {
net.PacketConn

// count conn reference
DecreaseCount()
IncreaseCount()
}

type conn struct {
quicConn quic.Connection
pconn *reuseConn
pconn pConn
transport *transport
scope network.ConnManagementScope

Expand Down
126 changes: 106 additions & 20 deletions p2p/transport/quic/conn_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,16 @@ import (

//go:generate sh -c "mockgen -package libp2pquic -destination mock_connection_gater_test.go github.com/libp2p/go-libp2p/core/connmgr ConnectionGater && goimports -w mock_connection_gater_test.go"

type connTestCase struct {
Name string
Options []Option
}

var connTestCases = []*connTestCase{
{"reuseport_on", []Option{}},
{"reuseport_off", []Option{DisableReuseport()}},
}

func createPeer(t *testing.T) (peer.ID, ic.PrivKey) {
var priv ic.PrivKey
var err error
Expand All @@ -49,20 +59,29 @@ func createPeer(t *testing.T) (peer.ID, ic.PrivKey) {

func runServer(t *testing.T, tr tpt.Transport, addr string) tpt.Listener {
t.Helper()

ln, err := tr.Listen(ma.StringCast(addr))
require.NoError(t, err)
return ln
}

func TestHandshake(t *testing.T) {
for _, tc := range connTestCases {
t.Run(tc.Name, func(t *testing.T) {
testHandshake(t, tc)
})
}
}

func testHandshake(t *testing.T, tc *connTestCase) {
serverID, serverKey := createPeer(t)
clientID, clientKey := createPeer(t)
serverTransport, err := NewTransport(serverKey, nil, nil, nil)
serverTransport, err := NewTransport(serverKey, nil, nil, nil, tc.Options...)
require.NoError(t, err)
defer serverTransport.(io.Closer).Close()

handshake := func(t *testing.T, ln tpt.Listener) {
clientTransport, err := NewTransport(clientKey, nil, nil, nil)
clientTransport, err := NewTransport(clientKey, nil, nil, nil, tc.Options...)
require.NoError(t, err)
defer clientTransport.(io.Closer).Close()
conn, err := clientTransport.Dial(context.Background(), ln.Multiaddr(), serverID)
Expand Down Expand Up @@ -97,22 +116,30 @@ func TestHandshake(t *testing.T) {
}

func TestResourceManagerSuccess(t *testing.T) {
for _, tc := range connTestCases {
t.Run(tc.Name, func(t *testing.T) {
testResourceManagerSuccess(t, tc)
})
}
}

func testResourceManagerSuccess(t *testing.T, tc *connTestCase) {
serverID, serverKey := createPeer(t)
clientID, clientKey := createPeer(t)

ctrl := gomock.NewController(t)
defer ctrl.Finish()

serverRcmgr := mocknetwork.NewMockResourceManager(ctrl)
serverTransport, err := NewTransport(serverKey, nil, nil, serverRcmgr)
serverTransport, err := NewTransport(serverKey, nil, nil, serverRcmgr, tc.Options...)
require.NoError(t, err)
defer serverTransport.(io.Closer).Close()
ln, err := serverTransport.Listen(ma.StringCast("/ip4/127.0.0.1/udp/0/quic"))
require.NoError(t, err)
defer ln.Close()

clientRcmgr := mocknetwork.NewMockResourceManager(ctrl)
clientTransport, err := NewTransport(clientKey, nil, nil, clientRcmgr)
clientTransport, err := NewTransport(clientKey, nil, nil, clientRcmgr, tc.Options...)
require.NoError(t, err)
defer clientTransport.(io.Closer).Close()

Expand Down Expand Up @@ -140,12 +167,20 @@ func TestResourceManagerSuccess(t *testing.T) {
}

func TestResourceManagerDialDenied(t *testing.T) {
for _, tc := range connTestCases {
t.Run(tc.Name, func(t *testing.T) {
testResourceManagerDialDenied(t, tc)
})
}
}

func testResourceManagerDialDenied(t *testing.T, tc *connTestCase) {
_, clientKey := createPeer(t)
ctrl := gomock.NewController(t)
defer ctrl.Finish()

rcmgr := mocknetwork.NewMockResourceManager(ctrl)
clientTransport, err := NewTransport(clientKey, nil, nil, rcmgr)
clientTransport, err := NewTransport(clientKey, nil, nil, rcmgr, tc.Options...)
require.NoError(t, err)
defer clientTransport.(io.Closer).Close()

Expand All @@ -160,16 +195,25 @@ func TestResourceManagerDialDenied(t *testing.T) {

_, err = clientTransport.Dial(context.Background(), target, p)
require.ErrorIs(t, err, rerr)

}

func TestResourceManagerAcceptDenied(t *testing.T) {
for _, tc := range connTestCases {
t.Run(tc.Name, func(t *testing.T) {
testResourceManagerAcceptDenied(t, tc)
})
}
}

func testResourceManagerAcceptDenied(t *testing.T, tc *connTestCase) {
serverID, serverKey := createPeer(t)
clientID, clientKey := createPeer(t)
ctrl := gomock.NewController(t)
defer ctrl.Finish()

clientRcmgr := mocknetwork.NewMockResourceManager(ctrl)
clientTransport, err := NewTransport(clientKey, nil, nil, clientRcmgr)
clientTransport, err := NewTransport(clientKey, nil, nil, clientRcmgr, tc.Options...)
require.NoError(t, err)
defer clientTransport.(io.Closer).Close()

Expand All @@ -181,7 +225,7 @@ func TestResourceManagerAcceptDenied(t *testing.T) {
serverConnScope.EXPECT().SetPeer(clientID).Return(rerr),
serverConnScope.EXPECT().Done(),
)
serverTransport, err := NewTransport(serverKey, nil, nil, serverRcmgr)
serverTransport, err := NewTransport(serverKey, nil, nil, serverRcmgr, tc.Options...)
require.NoError(t, err)
defer serverTransport.(io.Closer).Close()
ln, err := serverTransport.Listen(ma.StringCast("/ip4/127.0.0.1/udp/0/quic"))
Expand Down Expand Up @@ -213,16 +257,24 @@ func TestResourceManagerAcceptDenied(t *testing.T) {
}

func TestStreams(t *testing.T) {
for _, tc := range connTestCases {
t.Run(tc.Name, func(t *testing.T) {
testStreams(t, tc)
})
}
}

func testStreams(t *testing.T, tc *connTestCase) {
serverID, serverKey := createPeer(t)
_, clientKey := createPeer(t)

serverTransport, err := NewTransport(serverKey, nil, nil, nil)
serverTransport, err := NewTransport(serverKey, nil, nil, nil, tc.Options...)
require.NoError(t, err)
defer serverTransport.(io.Closer).Close()
ln := runServer(t, serverTransport, "/ip4/127.0.0.1/udp/0/quic")
defer ln.Close()

clientTransport, err := NewTransport(clientKey, nil, nil, nil)
clientTransport, err := NewTransport(clientKey, nil, nil, nil, tc.Options...)
require.NoError(t, err)
defer clientTransport.(io.Closer).Close()
conn, err := clientTransport.Dial(context.Background(), ln.Multiaddr(), serverID)
Expand All @@ -245,16 +297,24 @@ func TestStreams(t *testing.T) {
}

func TestHandshakeFailPeerIDMismatch(t *testing.T) {
for _, tc := range connTestCases {
t.Run(tc.Name, func(t *testing.T) {
testHandshakeFailPeerIDMismatch(t, tc)
})
}
}

func testHandshakeFailPeerIDMismatch(t *testing.T, tc *connTestCase) {
_, serverKey := createPeer(t)
_, clientKey := createPeer(t)
thirdPartyID, _ := createPeer(t)

serverTransport, err := NewTransport(serverKey, nil, nil, nil)
serverTransport, err := NewTransport(serverKey, nil, nil, nil, tc.Options...)
require.NoError(t, err)
defer serverTransport.(io.Closer).Close()
ln := runServer(t, serverTransport, "/ip4/127.0.0.1/udp/0/quic")

clientTransport, err := NewTransport(clientKey, nil, nil, nil)
clientTransport, err := NewTransport(clientKey, nil, nil, nil, tc.Options...)
require.NoError(t, err)
// dial, but expect the wrong peer ID
_, err = clientTransport.Dial(context.Background(), ln.Multiaddr(), thirdPartyID)
Expand All @@ -279,6 +339,14 @@ func TestHandshakeFailPeerIDMismatch(t *testing.T) {
}

func TestConnectionGating(t *testing.T) {
for _, tc := range connTestCases {
t.Run(tc.Name, func(t *testing.T) {
testConnectionGating(t, tc)
})
}
}

func testConnectionGating(t *testing.T, tc *connTestCase) {
serverID, serverKey := createPeer(t)
_, clientKey := createPeer(t)

Expand All @@ -287,7 +355,7 @@ func TestConnectionGating(t *testing.T) {
cg := NewMockConnectionGater(mockCtrl)

t.Run("accepted connections", func(t *testing.T) {
serverTransport, err := NewTransport(serverKey, nil, cg, nil)
serverTransport, err := NewTransport(serverKey, nil, cg, nil, tc.Options...)
defer serverTransport.(io.Closer).Close()
require.NoError(t, err)
ln := runServer(t, serverTransport, "/ip4/127.0.0.1/udp/0/quic")
Expand All @@ -302,7 +370,7 @@ func TestConnectionGating(t *testing.T) {
require.NoError(t, err)
}()

clientTransport, err := NewTransport(clientKey, nil, nil, nil)
clientTransport, err := NewTransport(clientKey, nil, nil, nil, tc.Options...)
require.NoError(t, err)
defer clientTransport.(io.Closer).Close()
// make sure that connection attempts fails
Expand Down Expand Up @@ -332,7 +400,7 @@ func TestConnectionGating(t *testing.T) {
})

t.Run("secured connections", func(t *testing.T) {
serverTransport, err := NewTransport(serverKey, nil, nil, nil)
serverTransport, err := NewTransport(serverKey, nil, nil, nil, tc.Options...)
require.NoError(t, err)
defer serverTransport.(io.Closer).Close()
ln := runServer(t, serverTransport, "/ip4/127.0.0.1/udp/0/quic")
Expand All @@ -341,7 +409,7 @@ func TestConnectionGating(t *testing.T) {
cg := NewMockConnectionGater(mockCtrl)
cg.EXPECT().InterceptSecured(gomock.Any(), gomock.Any(), gomock.Any())

clientTransport, err := NewTransport(clientKey, nil, cg, nil)
clientTransport, err := NewTransport(clientKey, nil, cg, nil, tc.Options...)
require.NoError(t, err)
defer clientTransport.(io.Closer).Close()

Expand All @@ -360,16 +428,24 @@ func TestConnectionGating(t *testing.T) {
}

func TestDialTwo(t *testing.T) {
for _, tc := range connTestCases {
t.Run(tc.Name, func(t *testing.T) {
testDialTwo(t, tc)
})
}
}

func testDialTwo(t *testing.T, tc *connTestCase) {
serverID, serverKey := createPeer(t)
_, clientKey := createPeer(t)
serverID2, serverKey2 := createPeer(t)

serverTransport, err := NewTransport(serverKey, nil, nil, nil)
serverTransport, err := NewTransport(serverKey, nil, nil, nil, tc.Options...)
require.NoError(t, err)
defer serverTransport.(io.Closer).Close()
ln1 := runServer(t, serverTransport, "/ip4/127.0.0.1/udp/0/quic")
defer ln1.Close()
serverTransport2, err := NewTransport(serverKey2, nil, nil, nil)
serverTransport2, err := NewTransport(serverKey2, nil, nil, nil, tc.Options...)
require.NoError(t, err)
defer serverTransport2.(io.Closer).Close()
ln2 := runServer(t, serverTransport2, "/ip4/127.0.0.1/udp/0/quic")
Expand All @@ -395,7 +471,7 @@ func TestDialTwo(t *testing.T) {
}
}()

clientTransport, err := NewTransport(clientKey, nil, nil, nil)
clientTransport, err := NewTransport(clientKey, nil, nil, nil, tc.Options...)
require.NoError(t, err)
defer clientTransport.(io.Closer).Close()
c1, err := clientTransport.Dial(context.Background(), ln1.Multiaddr(), serverID)
Expand Down Expand Up @@ -432,6 +508,14 @@ func TestDialTwo(t *testing.T) {
}

func TestStatelessReset(t *testing.T) {
for _, tc := range connTestCases {
t.Run(tc.Name, func(t *testing.T) {
testStatelessReset(t, tc)
})
}
}

func testStatelessReset(t *testing.T, tc *connTestCase) {
origGarbageCollectInterval := garbageCollectInterval
origMaxUnusedDuration := maxUnusedDuration

Expand All @@ -446,7 +530,7 @@ func TestStatelessReset(t *testing.T) {
serverID, serverKey := createPeer(t)
_, clientKey := createPeer(t)

serverTransport, err := NewTransport(serverKey, nil, nil, nil)
serverTransport, err := NewTransport(serverKey, nil, nil, nil, tc.Options...)
require.NoError(t, err)
defer serverTransport.(io.Closer).Close()
ln := runServer(t, serverTransport, "/ip4/127.0.0.1/udp/0/quic")
Expand All @@ -463,7 +547,7 @@ func TestStatelessReset(t *testing.T) {
defer proxy.Close()

// establish a connection
clientTransport, err := NewTransport(clientKey, nil, nil, nil)
clientTransport, err := NewTransport(clientKey, nil, nil, nil, tc.Options...)
require.NoError(t, err)
defer clientTransport.(io.Closer).Close()
proxyAddr, err := toQuicMultiaddr(proxy.LocalAddr())
Expand Down Expand Up @@ -508,6 +592,8 @@ func TestStatelessReset(t *testing.T) {
require.Contains(t, rerr.Error(), "received a stateless reset")
}

// Hole punching is only expected to work with reuseport enabled.
// We don't need to test `DisableReuseport` option.
func TestHolePunching(t *testing.T) {
serverID, serverKey := createPeer(t)
clientID, clientKey := createPeer(t)
Expand Down
Loading

0 comments on commit 0f4a969

Please sign in to comment.