mirror of
https://source.quilibrium.com/quilibrium/ceremonyclient.git
synced 2024-12-30 18:35:18 +00:00
274 lines
8.5 KiB
Go
274 lines
8.5 KiB
Go
|
package quicreuse
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"crypto/rand"
|
||
|
"crypto/tls"
|
||
|
"fmt"
|
||
|
"net"
|
||
|
"runtime"
|
||
|
"testing"
|
||
|
"time"
|
||
|
|
||
|
"github.com/libp2p/go-libp2p/core/crypto"
|
||
|
"github.com/libp2p/go-libp2p/core/peer"
|
||
|
libp2ptls "github.com/libp2p/go-libp2p/p2p/security/tls"
|
||
|
|
||
|
ma "github.com/multiformats/go-multiaddr"
|
||
|
manet "github.com/multiformats/go-multiaddr/net"
|
||
|
"github.com/quic-go/quic-go"
|
||
|
"github.com/stretchr/testify/require"
|
||
|
)
|
||
|
|
||
|
func checkClosed(t *testing.T, cm *ConnManager) {
|
||
|
for _, r := range []*reuse{cm.reuseUDP4, cm.reuseUDP6} {
|
||
|
if r == nil {
|
||
|
continue
|
||
|
}
|
||
|
r.mutex.Lock()
|
||
|
for _, tr := range r.globalListeners {
|
||
|
require.Zero(t, tr.GetCount())
|
||
|
}
|
||
|
for _, trs := range r.unicast {
|
||
|
for _, tr := range trs {
|
||
|
require.Zero(t, tr.GetCount())
|
||
|
}
|
||
|
}
|
||
|
r.mutex.Unlock()
|
||
|
}
|
||
|
require.Eventually(t, func() bool { return !isGarbageCollectorRunning() }, 200*time.Millisecond, 10*time.Millisecond)
|
||
|
}
|
||
|
|
||
|
func TestListenQUICDraft29Disabled(t *testing.T) {
|
||
|
cm, err := NewConnManager([32]byte{}, DisableDraft29(), DisableReuseport())
|
||
|
require.NoError(t, err)
|
||
|
defer cm.Close()
|
||
|
_, err = cm.ListenQUIC(ma.StringCast("/ip4/127.0.0.1/udp/0/quic"), &tls.Config{}, nil)
|
||
|
require.EqualError(t, err, "can't listen on `/quic` multiaddr (QUIC draft 29 version) when draft 29 support is disabled")
|
||
|
ln, err := cm.ListenQUIC(ma.StringCast("/ip4/127.0.0.1/udp/0/quic-v1"), &tls.Config{NextProtos: []string{"proto"}}, nil)
|
||
|
require.NoError(t, err)
|
||
|
require.NoError(t, ln.Close())
|
||
|
require.False(t, isGarbageCollectorRunning())
|
||
|
}
|
||
|
|
||
|
func TestListenOnSameProto(t *testing.T) {
|
||
|
t.Run("with reuseport", func(t *testing.T) {
|
||
|
testListenOnSameProto(t, true)
|
||
|
})
|
||
|
|
||
|
t.Run("without reuseport", func(t *testing.T) {
|
||
|
testListenOnSameProto(t, false)
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func testListenOnSameProto(t *testing.T, enableReuseport bool) {
|
||
|
var opts []Option
|
||
|
if !enableReuseport {
|
||
|
opts = append(opts, DisableReuseport())
|
||
|
}
|
||
|
cm, err := NewConnManager([32]byte{}, opts...)
|
||
|
require.NoError(t, err)
|
||
|
defer checkClosed(t, cm)
|
||
|
defer cm.Close()
|
||
|
|
||
|
const alpn = "proto"
|
||
|
|
||
|
var tlsConf tls.Config
|
||
|
tlsConf.NextProtos = []string{alpn}
|
||
|
ln1, err := cm.ListenQUIC(ma.StringCast("/ip4/127.0.0.1/udp/0/quic-v1"), &tls.Config{NextProtos: []string{alpn}}, nil)
|
||
|
require.NoError(t, err)
|
||
|
defer ln1.Close()
|
||
|
|
||
|
addr := ma.StringCast(fmt.Sprintf("/ip4/127.0.0.1/udp/%d/quic-v1", ln1.Addr().(*net.UDPAddr).Port))
|
||
|
_, err = cm.ListenQUIC(addr, &tls.Config{NextProtos: []string{alpn}}, nil)
|
||
|
require.EqualError(t, err, "already listening for protocol "+alpn)
|
||
|
|
||
|
// listening on a different address works
|
||
|
ln2, err := cm.ListenQUIC(ma.StringCast("/ip4/127.0.0.1/udp/0/quic-v1"), &tls.Config{NextProtos: []string{alpn}}, nil)
|
||
|
require.NoError(t, err)
|
||
|
defer ln2.Close()
|
||
|
}
|
||
|
|
||
|
// The conn passed to quic-go should be a conn that quic-go can be
|
||
|
// type-asserted to a UDPConn. That way, it can use all kinds of optimizations.
|
||
|
func TestConnectionPassedToQUICForListening(t *testing.T) {
|
||
|
if runtime.GOOS == "windows" {
|
||
|
t.Skip("skipping on windows. Windows doesn't support these optimizations")
|
||
|
}
|
||
|
cm, err := NewConnManager([32]byte{}, DisableReuseport())
|
||
|
require.NoError(t, err)
|
||
|
defer cm.Close()
|
||
|
|
||
|
raddr := ma.StringCast("/ip4/127.0.0.1/udp/0/quic-v1")
|
||
|
|
||
|
naddr, _, err := FromQuicMultiaddr(raddr)
|
||
|
require.NoError(t, err)
|
||
|
netw, _, err := manet.DialArgs(raddr)
|
||
|
require.NoError(t, err)
|
||
|
|
||
|
_, err = cm.ListenQUIC(raddr, &tls.Config{NextProtos: []string{"proto"}}, nil)
|
||
|
require.NoError(t, err)
|
||
|
quicTr, err := cm.transportForListen(netw, naddr)
|
||
|
require.NoError(t, err)
|
||
|
defer quicTr.Close()
|
||
|
if _, ok := quicTr.(*singleOwnerTransport).Transport.Conn.(quic.OOBCapablePacketConn); !ok {
|
||
|
t.Fatal("connection passed to quic-go cannot be type asserted to a *net.UDPConn")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestAcceptErrorGetCleanedUp(t *testing.T) {
|
||
|
raddr := ma.StringCast("/ip4/127.0.0.1/udp/0/quic-v1")
|
||
|
|
||
|
cm, err := NewConnManager([32]byte{}, DisableReuseport())
|
||
|
require.NoError(t, err)
|
||
|
defer cm.Close()
|
||
|
|
||
|
originalNumberOfGoroutines := runtime.NumGoroutine()
|
||
|
t.Log("num goroutines:", originalNumberOfGoroutines)
|
||
|
|
||
|
// This spawns a background goroutine for the listener
|
||
|
l, err := cm.ListenQUIC(raddr, &tls.Config{NextProtos: []string{"proto"}}, nil)
|
||
|
require.NoError(t, err)
|
||
|
|
||
|
// We spawned a goroutine for the listener
|
||
|
require.Greater(t, runtime.NumGoroutine(), originalNumberOfGoroutines)
|
||
|
l.Close()
|
||
|
|
||
|
// Now make sure we have less goroutines than before
|
||
|
// Manually doing the same as require.Eventually, except avoiding adding a goroutine
|
||
|
goRoutinesCleanedUp := false
|
||
|
for i := 0; i < 50; i++ {
|
||
|
t.Log("num goroutines:", runtime.NumGoroutine())
|
||
|
if runtime.NumGoroutine() <= originalNumberOfGoroutines {
|
||
|
goRoutinesCleanedUp = true
|
||
|
break
|
||
|
}
|
||
|
time.Sleep(100 * time.Millisecond)
|
||
|
}
|
||
|
|
||
|
require.True(t, goRoutinesCleanedUp, "goroutines were not cleaned up")
|
||
|
}
|
||
|
|
||
|
// The connection passed to quic-go needs to be type-assertable to a net.UDPConn,
|
||
|
// in order to enable features like batch processing and ECN.
|
||
|
func TestConnectionPassedToQUICForDialing(t *testing.T) {
|
||
|
if runtime.GOOS == "windows" {
|
||
|
t.Skip("skipping on windows. Windows doesn't support these optimizations")
|
||
|
}
|
||
|
cm, err := NewConnManager([32]byte{}, DisableReuseport())
|
||
|
require.NoError(t, err)
|
||
|
defer cm.Close()
|
||
|
|
||
|
raddr := ma.StringCast("/ip4/127.0.0.1/udp/1234/quic-v1")
|
||
|
|
||
|
naddr, _, err := FromQuicMultiaddr(raddr)
|
||
|
require.NoError(t, err)
|
||
|
netw, _, err := manet.DialArgs(raddr)
|
||
|
require.NoError(t, err)
|
||
|
|
||
|
quicTr, err := cm.TransportForDial(netw, naddr)
|
||
|
|
||
|
require.NoError(t, err, "dial error")
|
||
|
defer quicTr.Close()
|
||
|
if _, ok := quicTr.(*singleOwnerTransport).Transport.Conn.(quic.OOBCapablePacketConn); !ok {
|
||
|
t.Fatal("connection passed to quic-go cannot be type asserted to a *net.UDPConn")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func getTLSConfForProto(t *testing.T, alpn string) (peer.ID, *tls.Config) {
|
||
|
t.Helper()
|
||
|
priv, _, err := crypto.GenerateEd25519Key(rand.Reader)
|
||
|
require.NoError(t, err)
|
||
|
id, err := peer.IDFromPrivateKey(priv)
|
||
|
require.NoError(t, err)
|
||
|
// We use the libp2p TLS certificate here, just because it's convenient.
|
||
|
identity, err := libp2ptls.NewIdentity(priv)
|
||
|
require.NoError(t, err)
|
||
|
var tlsConf tls.Config
|
||
|
tlsConf.NextProtos = []string{alpn}
|
||
|
tlsConf.GetConfigForClient = func(info *tls.ClientHelloInfo) (*tls.Config, error) {
|
||
|
c, _ := identity.ConfigForPeer("")
|
||
|
c.NextProtos = tlsConf.NextProtos
|
||
|
return c, nil
|
||
|
}
|
||
|
return id, &tlsConf
|
||
|
}
|
||
|
|
||
|
func connectWithProtocol(t *testing.T, addr net.Addr, alpn string) (peer.ID, error) {
|
||
|
t.Helper()
|
||
|
clientKey, _, err := crypto.GenerateEd25519Key(rand.Reader)
|
||
|
require.NoError(t, err)
|
||
|
clientIdentity, err := libp2ptls.NewIdentity(clientKey)
|
||
|
require.NoError(t, err)
|
||
|
tlsConf, peerChan := clientIdentity.ConfigForPeer("")
|
||
|
cconn, err := net.ListenUDP("udp4", nil)
|
||
|
tlsConf.NextProtos = []string{alpn}
|
||
|
require.NoError(t, err)
|
||
|
c, err := quic.Dial(context.Background(), cconn, addr, tlsConf, nil)
|
||
|
if err != nil {
|
||
|
return "", err
|
||
|
}
|
||
|
defer c.CloseWithError(0, "")
|
||
|
require.Equal(t, alpn, c.ConnectionState().TLS.NegotiatedProtocol)
|
||
|
serverID, err := peer.IDFromPublicKey(<-peerChan)
|
||
|
require.NoError(t, err)
|
||
|
return serverID, nil
|
||
|
}
|
||
|
|
||
|
func TestListener(t *testing.T) {
|
||
|
t.Run("with reuseport", func(t *testing.T) {
|
||
|
testListener(t, true)
|
||
|
})
|
||
|
|
||
|
t.Run("without reuseport", func(t *testing.T) {
|
||
|
testListener(t, false)
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func testListener(t *testing.T, enableReuseport bool) {
|
||
|
var opts []Option
|
||
|
if !enableReuseport {
|
||
|
opts = append(opts, DisableReuseport())
|
||
|
}
|
||
|
cm, err := NewConnManager([32]byte{}, opts...)
|
||
|
require.NoError(t, err)
|
||
|
|
||
|
id1, tlsConf1 := getTLSConfForProto(t, "proto1")
|
||
|
ln1, err := cm.ListenQUIC(ma.StringCast("/ip4/127.0.0.1/udp/0/quic-v1"), tlsConf1, nil)
|
||
|
require.NoError(t, err)
|
||
|
|
||
|
id2, tlsConf2 := getTLSConfForProto(t, "proto2")
|
||
|
ln2, err := cm.ListenQUIC(
|
||
|
ma.StringCast(fmt.Sprintf("/ip4/127.0.0.1/udp/%d/quic-v1", ln1.Addr().(*net.UDPAddr).Port)),
|
||
|
tlsConf2,
|
||
|
nil,
|
||
|
)
|
||
|
require.NoError(t, err)
|
||
|
require.Equal(t, ln1.Addr(), ln2.Addr())
|
||
|
|
||
|
// Test that the right certificate is served.
|
||
|
id, err := connectWithProtocol(t, ln1.Addr(), "proto1")
|
||
|
require.NoError(t, err)
|
||
|
require.Equal(t, id1, id)
|
||
|
id, err = connectWithProtocol(t, ln1.Addr(), "proto2")
|
||
|
require.NoError(t, err)
|
||
|
require.Equal(t, id2, id)
|
||
|
// No such protocol registered.
|
||
|
_, err = connectWithProtocol(t, ln1.Addr(), "proto3")
|
||
|
require.Error(t, err)
|
||
|
|
||
|
// Now close the first listener to test that it's properly deregistered.
|
||
|
require.NoError(t, ln1.Close())
|
||
|
_, err = connectWithProtocol(t, ln1.Addr(), "proto1")
|
||
|
require.Error(t, err)
|
||
|
// connecting to the other listener should still be possible
|
||
|
id, err = connectWithProtocol(t, ln1.Addr(), "proto2")
|
||
|
require.NoError(t, err)
|
||
|
require.Equal(t, id2, id)
|
||
|
|
||
|
ln2.Close()
|
||
|
cm.Close()
|
||
|
|
||
|
checkClosed(t, cm)
|
||
|
}
|