mirror of
				https://source.quilibrium.com/quilibrium/ceremonyclient.git
				synced 2025-11-04 07:07:26 +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)
 | 
						|
}
 |