Repository: libp2p/go-libp2p
Pull request #1895 (2022-11-17 03:45:50)

webtransport: update webtransport-go to v0.3.0
Not really sure what to do with the error codes here. At some point, we probably should standardize libp2p error codes, and at that point, all the codes we've been using so far will become deprecated, so that we can distinguish between new and legacy nodes.


Patch:
diff --git a/config/config.go b/config/config.go
index ea674c0d..bee59754 100644
--- a/config/config.go
+++ b/config/config.go
@@ -2,6 +2,7 @@ package config
 
 import (
 	"crypto/rand"
+	"errors"
 	"fmt"
 	"time"
 
@@ -13,7 +14,10 @@ import (
 	"github.com/libp2p/go-libp2p/core/peer"
 	"github.com/libp2p/go-libp2p/core/peerstore"
 	"github.com/libp2p/go-libp2p/core/pnet"
+	"github.com/libp2p/go-libp2p/core/protocol"
 	"github.com/libp2p/go-libp2p/core/routing"
+	"github.com/libp2p/go-libp2p/core/sec"
+	"github.com/libp2p/go-libp2p/core/sec/insecure"
 	"github.com/libp2p/go-libp2p/core/transport"
 	"github.com/libp2p/go-libp2p/p2p/host/autonat"
 	"github.com/libp2p/go-libp2p/p2p/host/autorelay"
@@ -51,6 +55,11 @@ type AutoNATConfig struct {
 	ThrottleInterval    time.Duration
 }
 
+type Security struct {
+	ID          protocol.ID
+	Constructor interface{}
+}
+
 // Config describes a set of settings for a libp2p node
 //
 // This is *not* a stable interface. Use the options defined in the root
@@ -71,7 +80,7 @@ type Config struct {
 
 	Transports         []fx.Option
 	Muxers             []tptu.StreamMuxer
-	SecurityTransports []fx.Option
+	SecurityTransports []Security
 	Insecure           bool
 	PSK                pnet.PSK
 
@@ -167,20 +176,9 @@ func (cfg *Config) addTransports(h host.Host) error {
 		return fmt.Errorf("swarm does not support transports")
 	}
 
-	var security []fx.Option
-	if cfg.Insecure {
-		security = append(security, fx.Provide(makeInsecureTransport))
-	} else {
-		security = cfg.SecurityTransports
-	}
-
 	fxopts := []fx.Option{
 		fx.WithLogger(func() fxevent.Logger { return getFXLogger() }),
-		fx.Provide(tptu.New),
-		fx.Provide(fx.Annotate(
-			makeSecurityMuxer,
-			fx.ParamTags(`group:"security"`),
-		)),
+		fx.Provide(fx.Annotate(tptu.New, fx.ParamTags(`name:"security"`))),
 		fx.Supply(cfg.Muxers),
 		fx.Supply(h.ID()),
 		fx.Provide(func() host.Host { return h }),
@@ -191,8 +189,54 @@ func (cfg *Config) addTransports(h host.Host) error {
 		fx.Provide(func() *madns.Resolver { return cfg.MultiaddrResolver }),
 	}
 	fxopts = append(fxopts, cfg.Transports...)
-	if !cfg.Insecure {
-		fxopts = append(fxopts, security...)
+	if cfg.Insecure {
+		fxopts = append(fxopts,
+			fx.Provide(
+				fx.Annotate(
+					func(id peer.ID, priv crypto.PrivKey) []sec.SecureTransport {
+						return []sec.SecureTransport{insecure.NewWithIdentity(insecure.ID, id, priv)}
+					},
+					fx.ResultTags(`name:"security"`),
+				),
+			),
+		)
+	} else {
+		// fx groups are unordered, but we need to preserve the order of the security transports
+		// First of all, we construct the security transports that are needed,
+		// and save them to a group call security_unordered.
+		for _, s := range cfg.SecurityTransports {
+			fxName := fmt.Sprintf(`name:"security_%s"`, s.ID)
+			fxopts = append(fxopts, fx.Supply(fx.Annotate(s.ID, fx.ResultTags(fxName))))
+			fxopts = append(fxopts,
+				fx.Provide(fx.Annotate(
+					s.Constructor,
+					fx.ParamTags(fxName),
+					fx.As(new(sec.SecureTransport)),
+					fx.ResultTags(`group:"security_unordered"`),
+				)),
+			)
+		}
+		// Then we consume the group security_unordered, and order them by the user's preference.
+		fxopts = append(fxopts, fx.Provide(
+			fx.Annotate(
+				func(secs []sec.SecureTransport) ([]sec.SecureTransport, error) {
+					if len(secs) != len(cfg.SecurityTransports) {
+						return nil, errors.New("inconsistent length for security transports")
+					}
+					t := make([]sec.SecureTransport, 0, len(secs))
+					for _, s := range cfg.SecurityTransports {
+						for _, st := range secs {
+							if s.ID != st.ID() {
+								continue
+							}
+							t = append(t, st)
+						}
+					}
+					return t, nil
+				},
+				fx.ParamTags(`group:"security_unordered"`),
+				fx.ResultTags(`name:"security"`),
+			)))
 	}
 
 	fxopts = append(fxopts, fx.Invoke(
diff --git a/config/security.go b/config/security.go
deleted file mode 100644
index 1a88575f..00000000
--- a/config/security.go
+++ /dev/null
@@ -1,23 +0,0 @@
-package config
-
-import (
-	"github.com/libp2p/go-libp2p/core/crypto"
-	"github.com/libp2p/go-libp2p/core/peer"
-	"github.com/libp2p/go-libp2p/core/sec"
-	"github.com/libp2p/go-libp2p/core/sec/insecure"
-	csms "github.com/libp2p/go-libp2p/p2p/net/conn-security-multistream"
-)
-
-func makeInsecureTransport(id peer.ID, privKey crypto.PrivKey) sec.SecureMuxer {
-	secMuxer := new(csms.SSMuxer)
-	secMuxer.AddTransport(insecure.ID, insecure.NewWithIdentity(insecure.ID, id, privKey))
-	return secMuxer
-}
-
-func makeSecurityMuxer(tpts []sec.SecureTransport) sec.SecureMuxer {
-	secMuxer := new(csms.SSMuxer)
-	for _, tpt := range tpts {
-		secMuxer.AddTransport(string(tpt.ID()), tpt)
-	}
-	return secMuxer
-}
diff --git a/core/network/conn.go b/core/network/conn.go
index 18414b06..6d16d298 100644
--- a/core/network/conn.go
+++ b/core/network/conn.go
@@ -34,12 +34,14 @@ type Conn interface {
 	GetStreams() []Stream
 }
 
-// ConnectionState holds extra information releated to the ConnSecurity entity.
+// ConnectionState holds information about the connection.
 type ConnectionState struct {
-	// The next protocol used for stream muxer selection. This is derived from
-	// security protocol handshake, for example, Noise handshake payload or
-	// TLS/ALPN negotiation.
-	NextProto string
+	// The stream multiplexer used on this connection (if any). For example: /yamux/1.0.0
+	StreamMultiplexer string
+	// The security protocol used on this connection (if any). For example: /tls/1.0.0
+	Security string
+	// the transport used on this connection. For example: tcp
+	Transport string
 }
 
 // ConnSecurity is the interface that one can mix into a connection interface to
@@ -57,7 +59,7 @@ type ConnSecurity interface {
 	// RemotePublicKey returns the public key of the remote peer.
 	RemotePublicKey() ic.PubKey
 
-	// Connection state info of the secured connection.
+	// ConnState returns information about the connection state.
 	ConnState() ConnectionState
 }
 
diff --git a/core/sec/security.go b/core/sec/security.go
index 8b733b5d..83059d94 100644
--- a/core/sec/security.go
+++ b/core/sec/security.go
@@ -29,18 +29,3 @@ type SecureTransport interface {
 	// ID is the protocol ID of the security protocol.
 	ID() protocol.ID
 }
-
-// A SecureMuxer is a wrapper around SecureTransport which can select security protocols
-// and open outbound connections with simultaneous open.
-type SecureMuxer interface {
-	// SecureInbound secures an inbound connection.
-	// The returned boolean indicates whether the connection should be treated as a server
-	// connection; in the case of SecureInbound it should always be true.
-	// If p is empty, connections from any peer are accepted.
-	SecureInbound(ctx context.Context, insecure net.Conn, p peer.ID) (SecureConn, bool, error)
-
-	// SecureOutbound secures an outbound connection.
-	// The returned boolean indicates whether the connection should be treated as a server
-	// connection due to simultaneous open.
-	SecureOutbound(ctx context.Context, insecure net.Conn, p peer.ID) (SecureConn, bool, error)
-}
diff --git a/go.mod b/go.mod
index 1730ffa3..fc9c2b86 100644
--- a/go.mod
+++ b/go.mod
@@ -33,7 +33,7 @@ require (
 	github.com/libp2p/zeroconf/v2 v2.2.0
 	github.com/lucas-clemente/quic-go v0.31.0
 	github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd
-	github.com/marten-seemann/webtransport-go v0.2.0
+	github.com/marten-seemann/webtransport-go v0.3.0
 	github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b
 	github.com/minio/sha256-simd v1.0.0
 	github.com/mr-tron/base58 v1.2.0
diff --git a/go.sum b/go.sum
index ec35b6a1..37a7db8f 100644
--- a/go.sum
+++ b/go.sum
@@ -333,8 +333,8 @@ github.com/marten-seemann/qtls-go1-19 v0.1.1 h1:mnbxeq3oEyQxQXwI4ReCgW9DPoPR94sN
 github.com/marten-seemann/qtls-go1-19 v0.1.1/go.mod h1:5HTDWtVudo/WFsHKRNuOhWlbdjrfs5JHrYb0wIJqGpI=
 github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd h1:br0buuQ854V8u83wA0rVZ8ttrq5CpaPZdvrK0LP2lOk=
 github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd/go.mod h1:QuCEs1Nt24+FYQEqAAncTDPJIuGs+LxK1MCiFL25pMU=
-github.com/marten-seemann/webtransport-go v0.2.0 h1:987jPVqcyE3vF+CHNIxDhT0P21O+bI4fVF+0NoRujSo=
-github.com/marten-seemann/webtransport-go v0.2.0/go.mod h1:XmnWYsWXaxUF7kjeIIzLWPyS+q0OcBY5vA64NuyK0ps=
+github.com/marten-seemann/webtransport-go v0.3.0 h1:TqUSf7/qZN8bJyuGrDMz9nDrfMbgH8p7KqV3TYrkBgo=
+github.com/marten-seemann/webtransport-go v0.3.0/go.mod h1:4xcfySgZMLP4aG5GBGj1egP7NlpfwgYJ1WJMvPPiVMU=
 github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
 github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
 github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
diff --git a/options.go b/options.go
index 0ff63b5f..1da2a70d 100644
--- a/options.go
+++ b/options.go
@@ -19,7 +19,6 @@ import (
 	"github.com/libp2p/go-libp2p/core/peerstore"
 	"github.com/libp2p/go-libp2p/core/pnet"
 	"github.com/libp2p/go-libp2p/core/protocol"
-	"github.com/libp2p/go-libp2p/core/sec"
 	"github.com/libp2p/go-libp2p/core/transport"
 	"github.com/libp2p/go-libp2p/p2p/host/autorelay"
 	bhost "github.com/libp2p/go-libp2p/p2p/host/basic"
@@ -73,22 +72,7 @@ func Security(name string, constructor interface{}) Option {
 		if cfg.Insecure {
 			return fmt.Errorf("cannot use security transports with an insecure libp2p configuration")
 		}
-		fxName := fmt.Sprintf(`name:"%s"`, name)
-		// provide the name of the security transport
-		cfg.SecurityTransports = append(cfg.SecurityTransports,
-			fx.Provide(fx.Annotate(
-				func() protocol.ID { return protocol.ID(name) },
-				fx.ResultTags(fxName),
-			)),
-		)
-		cfg.SecurityTransports = append(cfg.SecurityTransports,
-			fx.Provide(fx.Annotate(
-				constructor,
-				fx.ParamTags(fxName),
-				fx.As(new(sec.SecureTransport)),
-				fx.ResultTags(`group:"security"`),
-			)),
-		)
+		cfg.SecurityTransports = append(cfg.SecurityTransports, config.Security{ID: protocol.ID(name), Constructor: constructor})
 		return nil
 	}
 }
diff --git a/p2p/net/conn-security-multistream/ssms.go b/p2p/net/conn-security-multistream/ssms.go
deleted file mode 100644
index 595d8dfd..00000000
--- a/p2p/net/conn-security-multistream/ssms.go
+++ /dev/null
@@ -1,110 +0,0 @@
-package csms
-
-import (
-	"context"
-	"fmt"
-	"log"
-	"net"
-
-	"github.com/libp2p/go-libp2p/core/peer"
-	"github.com/libp2p/go-libp2p/core/sec"
-
-	mss "github.com/multiformats/go-multistream"
-)
-
-// SSMuxer is a multistream stream security transport multiplexer.
-//
-// SSMuxer is safe to use without initialization. However, it's not safe to move
-// after use.
-type SSMuxer struct {
-	mux             mss.MultistreamMuxer
-	tpts            map[string]sec.SecureTransport
-	OrderPreference []string
-}
-
-var _ sec.SecureMuxer = (*SSMuxer)(nil)
-
-// AddTransport adds a stream security transport to this multistream muxer.
-//
-// This method is *not* thread-safe. It should be called only when initializing
-// the SSMuxer.
-func (sm *SSMuxer) AddTransport(path string, transport sec.SecureTransport) {
-	if sm.tpts == nil {
-		sm.tpts = make(map[string]sec.SecureTransport, 1)
-	}
-
-	sm.mux.AddHandler(path, nil)
-	sm.tpts[path] = transport
-	sm.OrderPreference = append(sm.OrderPreference, path)
-}
-
-// SecureInbound secures an inbound connection using this multistream
-// multiplexed stream security transport.
-func (sm *SSMuxer) SecureInbound(ctx context.Context, insecure net.Conn, p peer.ID) (sec.SecureConn, bool, error) {
-	tpt, _, err := sm.selectProto(ctx, insecure, true)
-	if err != nil {
-		return nil, false, err
-	}
-	sconn, err := tpt.SecureInbound(ctx, insecure, p)
-	return sconn, true, err
-}
-
-// SecureOutbound secures an outbound connection using this multistream
-// multiplexed stream security transport.
-func (sm *SSMuxer) SecureOutbound(ctx context.Context, insecure net.Conn, p peer.ID) (sec.SecureConn, bool, error) {
-	tpt, server, err := sm.selectProto(ctx, insecure, false)
-	if err != nil {
-		return nil, false, err
-	}
-
-	var sconn sec.SecureConn
-	if server {
-		sconn, err = tpt.SecureInbound(ctx, insecure, p)
-		if err != nil {
-			return nil, false, fmt.Errorf("failed to secure inbound connection: %s", err)
-		}
-		// ensure the correct peer connected to us
-		if sconn.RemotePeer() != p {
-			sconn.Close()
-			log.Printf("Handshake failed to properly authenticate peer. Authenticated %s, expected %s.", sconn.RemotePeer(), p)
-			return nil, false, fmt.Errorf("unexpected peer")
-		}
-	} else {
-		sconn, err = tpt.SecureOutbound(ctx, insecure, p)
-	}
-
-	return sconn, server, err
-}
-
-func (sm *SSMuxer) selectProto(ctx context.Context, insecure net.Conn, server bool) (sec.SecureTransport, bool, error) {
-	var proto string
-	var err error
-	var iamserver bool
-	done := make(chan struct{})
-	go func() {
-		defer close(done)
-		if server {
-			iamserver = true
-			proto, _, err = sm.mux.Negotiate(insecure)
-		} else {
-			proto, iamserver, err = mss.SelectWithSimopenOrFail(sm.OrderPreference, insecure)
-		}
-	}()
-
-	select {
-	case <-done:
-		if err != nil {
-			return nil, false, err
-		}
-		if tpt, ok := sm.tpts[proto]; ok {
-			return tpt, iamserver, nil
-		}
-		return nil, false, fmt.Errorf("selected unknown security transport")
-	case <-ctx.Done():
-		// We *must* do this. We have outstanding work on the connection
-		// and it's no longer safe to use.
-		insecure.Close()
-		<-done // wait to stop using the connection.
-		return nil, false, ctx.Err()
-	}
-}
diff --git a/p2p/net/swarm/swarm_dial.go b/p2p/net/swarm/swarm_dial.go
index da9b10c1..29703f77 100644
--- a/p2p/net/swarm/swarm_dial.go
+++ b/p2p/net/swarm/swarm_dial.go
@@ -269,6 +269,13 @@ func (s *Swarm) dialPeer(ctx context.Context, p peer.ID) (*Conn, error) {
 
 	conn, err = s.dsync.Dial(ctx, p)
 	if err == nil {
+		// Ensure we connected to the correct peer.
+		// This was most likely already checked by the security protocol, but it doesn't hurt do it again here.
+		if conn.RemotePeer() != p {
+			conn.Close()
+			log.Errorw("Handshake failed to properly authenticate peer", "authenticated", conn.RemotePeer(), "expected", p)
+			return nil, fmt.Errorf("unexpected peer")
+		}
 		return conn, nil
 	}
 
diff --git a/p2p/net/upgrader/conn.go b/p2p/net/upgrader/conn.go
index fa736491..7a079b29 100644
--- a/p2p/net/upgrader/conn.go
+++ b/p2p/net/upgrader/conn.go
@@ -16,7 +16,8 @@ type transportConn struct {
 	scope     network.ConnManagementScope
 	stat      network.ConnStats
 
-	muxer protocol.ID
+	muxer    protocol.ID
+	security protocol.ID
 }
 
 var _ transport.CapableConn = &transportConn{}
@@ -54,5 +55,9 @@ func (t *transportConn) Close() error {
 }
 
 func (t *transportConn) ConnState() network.ConnectionState {
-	return network.ConnectionState{NextProto: string(t.muxer)}
+	return network.ConnectionState{
+		StreamMultiplexer: string(t.muxer),
+		Security:          string(t.security),
+		Transport:         "tcp",
+	}
 }
diff --git a/p2p/net/upgrader/upgrader.go b/p2p/net/upgrader/upgrader.go
index 403034db..5a69efb0 100644
--- a/p2p/net/upgrader/upgrader.go
+++ b/p2p/net/upgrader/upgrader.go
@@ -49,15 +49,17 @@ type StreamMuxer struct {
 // Upgrader is a multistream upgrader that can upgrade an underlying connection
 // to a full transport connection (secure and multiplexed).
 type upgrader struct {
-	secure sec.SecureMuxer
-
 	psk       ipnet.PSK
 	connGater connmgr.ConnectionGater
 	rcmgr     network.ResourceManager
 
-	msmuxer  *mss.MultistreamMuxer
-	muxers   []StreamMuxer
-	muxerIDs []string
+	muxerMuxer *mss.MultistreamMuxer
+	muxers     []StreamMuxer
+	muxerIDs   []string
+
+	security      []sec.SecureTransport
+	securityMuxer *mss.MultistreamMuxer
+	securityIDs   []string
 
 	// AcceptTimeout is the maximum duration an Accept is allowed to take.
 	// This includes the time between accepting the raw network connection,
@@ -69,15 +71,16 @@ type upgrader struct {
 
 var _ transport.Upgrader = &upgrader{}
 
-func New(secureMuxer sec.SecureMuxer, muxers []StreamMuxer, psk ipnet.PSK, rcmgr network.ResourceManager, connGater connmgr.ConnectionGater, opts ...Option) (transport.Upgrader, error) {
+func New(security []sec.SecureTransport, muxers []StreamMuxer, psk ipnet.PSK, rcmgr network.ResourceManager, connGater connmgr.ConnectionGater, opts ...Option) (transport.Upgrader, error) {
 	u := &upgrader{
-		secure:        secureMuxer,
 		acceptTimeout: defaultAcceptTimeout,
 		rcmgr:         rcmgr,
 		connGater:     connGater,
 		psk:           psk,
-		msmuxer:       mss.NewMultistreamMuxer(),
+		muxerMuxer:    mss.NewMultistreamMuxer(),
 		muxers:        muxers,
+		security:      security,
+		securityMuxer: mss.NewMultistreamMuxer(),
 	}
 	for _, opt := range opts {
 		if err := opt(u); err != nil {
@@ -89,9 +92,14 @@ func New(secureMuxer sec.SecureMuxer, muxers []StreamMuxer, psk ipnet.PSK, rcmgr
 	}
 	u.muxerIDs = make([]string, 0, len(muxers))
 	for _, m := range muxers {
-		u.msmuxer.AddHandler(string(m.ID), nil)
+		u.muxerMuxer.AddHandler(string(m.ID), nil)
 		u.muxerIDs = append(u.muxerIDs, string(m.ID))
 	}
+	u.securityIDs = make([]string, 0, len(security))
+	for _, s := range security {
+		u.securityMuxer.AddHandler(string(s.ID()), nil)
+		u.securityIDs = append(u.securityIDs, string(s.ID()))
+	}
 	return u, nil
 }
 
@@ -144,7 +152,7 @@ func (u *upgrader) upgrade(ctx context.Context, t transport.Transport, maconn ma
 		return nil, ipnet.ErrNotInPrivateNetwork
 	}
 
-	sconn, server, err := u.setupSecurity(ctx, conn, p, dir)
+	sconn, security, server, err := u.setupSecurity(ctx, conn, p, dir)
 	if err != nil {
 		conn.Close()
 		return nil, fmt.Errorf("failed to negotiate security protocol: %s", err)
@@ -156,7 +164,7 @@ func (u *upgrader) upgrade(ctx context.Context, t transport.Transport, maconn ma
 			log.Errorw("failed to close connection", "peer", p, "addr", maconn.RemoteMultiaddr(), "error", err)
 		}
 		return nil, fmt.Errorf("gater rejected connection with peer %s and addr %s with direction %d",
-			sconn.RemotePeer().Pretty(), maconn.RemoteMultiaddr(), dir)
+			sconn.RemotePeer(), maconn.RemoteMultiaddr(), dir)
 	}
 	// Only call SetPeer if it hasn't already been set -- this can happen when we don't know
 	// the peer in advance and in some bug scenarios.
@@ -167,7 +175,7 @@ func (u *upgrader) upgrade(ctx context.Context, t transport.Transport, maconn ma
 				log.Errorw("failed to close connection", "peer", p, "addr", maconn.RemoteMultiaddr(), "error", err)
 			}
 			return nil, fmt.Errorf("resource manager connection with peer %s and addr %s with direction %d",
-				sconn.RemotePeer().Pretty(), maconn.RemoteMultiaddr(), dir)
+				sconn.RemotePeer(), maconn.RemoteMultiaddr(), dir)
 		}
 	}
 
@@ -185,15 +193,25 @@ func (u *upgrader) upgrade(ctx context.Context, t transport.Transport, maconn ma
 		stat:           stat,
 		scope:          connScope,
 		muxer:          muxer,
+		security:       security,
 	}
 	return tc, nil
 }
 
-func (u *upgrader) setupSecurity(ctx context.Context, conn net.Conn, p peer.ID, dir network.Direction) (sec.SecureConn, bool, error) {
-	if dir == network.DirInbound {
-		return u.secure.SecureInbound(ctx, conn, p)
+func (u *upgrader) setupSecurity(ctx context.Context, conn net.Conn, p peer.ID, dir network.Direction) (sec.SecureConn, protocol.ID, bool, error) {
+	isServer := dir == network.DirInbound
+	var st sec.SecureTransport
+	var err error
+	st, isServer, err = u.negotiateSecurity(ctx, conn, isServer)
+	if err != nil {
+		return nil, "", false, err
 	}
-	return u.secure.SecureOutbound(ctx, conn, p)
+	if isServer {
+		sconn, err := st.SecureInbound(ctx, conn, p)
+		return sconn, st.ID(), true, err
+	}
+	sconn, err := st.SecureOutbound(ctx, conn, p)
+	return sconn, st.ID(), false, err
 }
 
 func (u *upgrader) negotiateMuxer(nc net.Conn, isServer bool) (*StreamMuxer, error) {
@@ -203,7 +221,7 @@ func (u *upgrader) negotiateMuxer(nc net.Conn, isServer bool) (*StreamMuxer, err
 
 	var proto string
 	if isServer {
-		selected, _, err := u.msmuxer.Negotiate(nc)
+		selected, _, err := u.muxerMuxer.Negotiate(nc)
 		if err != nil {
 			return nil, err
 		}
@@ -236,7 +254,7 @@ func (u *upgrader) getMuxerByID(id string) *StreamMuxer {
 }
 
 func (u *upgrader) setupMuxer(ctx context.Context, conn sec.SecureConn, server bool, scope network.PeerScope) (protocol.ID, network.MuxedConn, error) {
-	muxerSelected := conn.ConnState().NextProto
+	muxerSelected := conn.ConnState().StreamMultiplexer
 	// Use muxer selected from security handshake if available. Otherwise fall back to multistream-selection.
 	if len(muxerSelected) > 0 {
 		m := u.getMuxerByID(muxerSelected)
@@ -279,3 +297,51 @@ func (u *upgrader) setupMuxer(ctx context.Context, conn sec.SecureConn, server b
 		return "", nil, ctx.Err()
 	}
 }
+
+func (u *upgrader) getSecurityByID(id string) sec.SecureTransport {
+	for _, s := range u.security {
+		if string(s.ID()) == id {
+			return s
+		}
+	}
+	return nil
+}
+
+func (u *upgrader) negotiateSecurity(ctx context.Context, insecure net.Conn, server bool) (sec.SecureTransport, bool, error) {
+	type result struct {
+		proto     string
+		iamserver bool
+		err       error
+	}
+
+	done := make(chan result, 1)
+	go func() {
+		if server {
+			var r result
+			r.iamserver = true
+			r.proto, _, r.err = u.securityMuxer.Negotiate(insecure)
+			done <- r
+			return
+		}
+		var r result
+		r.proto, r.iamserver, r.err = mss.SelectWithSimopenOrFail(u.securityIDs, insecure)
+		done <- r
+	}()
+
+	select {
+	case r := <-done:
+		if r.err != nil {
+			return nil, false, r.err
+		}
+		if s := u.getSecurityByID(r.proto); s != nil {
+			return s, r.iamserver, nil
+		}
+		return nil, false, fmt.Errorf("selected unknown security transport: %s", r.proto)
+	case <-ctx.Done():
+		// We *must* do this. We have outstanding work on the connection
+		// and it's no longer safe to use.
+		insecure.Close()
+		<-done // wait to stop using the connection.
+		return nil, false, ctx.Err()
+	}
+}
diff --git a/p2p/security/noise/session.go b/p2p/security/noise/session.go
index f7e75e64..93ce5217 100644
--- a/p2p/security/noise/session.go
+++ b/p2p/security/noise/session.go
@@ -136,7 +136,7 @@ func (s *secureSession) Close() error {
 
 func SessionWithConnState(s *secureSession, muxer string) *secureSession {
 	if s != nil {
-		s.connectionState.NextProto = muxer
+		s.connectionState.StreamMultiplexer = muxer
 	}
 	return s
 }
diff --git a/p2p/security/tls/transport.go b/p2p/security/tls/transport.go
index a78abec2..b036bf89 100644
--- a/p2p/security/tls/transport.go
+++ b/p2p/security/tls/transport.go
@@ -171,7 +171,7 @@ func (t *Transport) setupConn(tlsConn *tls.Conn, remotePubKey ci.PubKey) (sec.Se
 		privKey:         t.privKey,
 		remotePeer:      remotePeerID,
 		remotePubKey:    remotePubKey,
-		connectionState: network.ConnectionState{NextProto: nextProto},
+		connectionState: network.ConnectionState{StreamMultiplexer: nextProto},
 	}, nil
 }
 
diff --git a/p2p/transport/quic/conn.go b/p2p/transport/quic/conn.go
index 1af53f2a..cc74b68c 100644
--- a/p2p/transport/quic/conn.go
+++ b/p2p/transport/quic/conn.go
@@ -71,45 +71,32 @@ func (c *conn) AcceptStream() (network.MuxedStream, error) {
 }
 
 // LocalPeer returns our peer ID
-func (c *conn) LocalPeer() peer.ID {
-	return c.localPeer
-}
+func (c *conn) LocalPeer() peer.ID { return c.localPeer }
 
 // LocalPrivateKey returns our private key
-func (c *conn) LocalPrivateKey() ic.PrivKey {
-	return c.privKey
-}
+func (c *conn) LocalPrivateKey() ic.PrivKey { return c.privKey }
 
 // RemotePeer returns the peer ID of the remote peer.
-func (c *conn) RemotePeer() peer.ID {
-	return c.remotePeerID
-}
+func (c *conn) RemotePeer() peer.ID { return c.remotePeerID }
 
 // RemotePublicKey returns the public key of the remote peer.
-func (c *conn) RemotePublicKey() ic.PubKey {
-	return c.remotePubKey
-}
-
-// ConnState is the state of security connection.
-// It is empty if not supported.
-func (c *conn) ConnState() network.ConnectionState {
-	return network.ConnectionState{}
-}
+func (c *conn) RemotePublicKey() ic.PubKey { return c.remotePubKey }
 
 // LocalMultiaddr returns the local Multiaddr associated
-func (c *conn) LocalMultiaddr() ma.Multiaddr {
-	return c.localMultiaddr
-}
+func (c *conn) LocalMultiaddr() ma.Multiaddr { return c.localMultiaddr }
 
 // RemoteMultiaddr returns the remote Multiaddr associated
-func (c *conn) RemoteMultiaddr() ma.Multiaddr {
-	return c.remoteMultiaddr
-}
+func (c *conn) RemoteMultiaddr() ma.Multiaddr { return c.remoteMultiaddr }
 
-func (c *conn) Transport() tpt.Transport {
-	return c.transport
-}
+func (c *conn) Transport() tpt.Transport { return c.transport }
 
-func (c *conn) Scope() network.ConnScope {
-	return c.scope
+func (c *conn) Scope() network.ConnScope { return c.scope }
+
+// ConnState is the state of security connection.
+func (c *conn) ConnState() network.ConnectionState {
+	t := "quic-v1"
+	if _, err := c.LocalMultiaddr().ValueForProtocol(ma.P_QUIC); err == nil {
+		t = "quic"
+	}
+	return network.ConnectionState{Transport: t}
 }
diff --git a/p2p/transport/websocket/conn.go b/p2p/transport/websocket/conn.go
index 6f2e0a76..30b70055 100644
--- a/p2p/transport/websocket/conn.go
+++ b/p2p/transport/websocket/conn.go
@@ -6,6 +6,9 @@ import (
 	"sync"
 	"time"
 
+	"github.com/libp2p/go-libp2p/core/network"
+	"github.com/libp2p/go-libp2p/core/transport"
+
 	ws "github.com/gorilla/websocket"
 )
 
@@ -149,3 +152,13 @@ func (c *Conn) SetWriteDeadline(t time.Time) error {
 
 	return c.Conn.SetWriteDeadline(t)
 }
+
+type capableConn struct {
+	transport.CapableConn
+}
+
+func (c *capableConn) ConnState() network.ConnectionState {
+	cs := c.CapableConn.ConnState()
+	cs.Transport = "websocket"
+	return cs
+}
diff --git a/p2p/transport/websocket/listener.go b/p2p/transport/websocket/listener.go
index ee681080..c9991d71 100644
--- a/p2p/transport/websocket/listener.go
+++ b/p2p/transport/websocket/listener.go
@@ -6,6 +6,8 @@ import (
 	"net"
 	"net/http"
 
+	"github.com/libp2p/go-libp2p/core/transport"
+
 	ma "github.com/multiformats/go-multiaddr"
 	manet "github.com/multiformats/go-multiaddr/net"
 )
@@ -140,3 +142,15 @@ func (l *listener) Multiaddr() ma.Multiaddr {
 func (l *listener) Multiaddrs() []ma.Multiaddr {
 	return []ma.Multiaddr{l.laddr}
 }
+
+type transportListener struct {
+	transport.Listener
+}
+
+func (l *transportListener) Accept() (transport.CapableConn, error) {
+	conn, err := l.Listener.Accept()
+	if err != nil {
+		return nil, err
+	}
+	return &capableConn{CapableConn: conn}, nil
+}
diff --git a/p2p/transport/websocket/websocket.go b/p2p/transport/websocket/websocket.go
index b693ef43..03941013 100644
--- a/p2p/transport/websocket/websocket.go
+++ b/p2p/transport/websocket/websocket.go
@@ -166,7 +166,11 @@ func (t *WebsocketTransport) Dial(ctx context.Context, raddr ma.Multiaddr, p pee
 		connScope.Done()
 		return nil, err
 	}
-	return t.upgrader.Upgrade(ctx, t, macon, network.DirOutbound, p, connScope)
+	conn, err := t.upgrader.Upgrade(ctx, t, macon, network.DirOutbound, p, connScope)
+	if err != nil {
+		return nil, err
+	}
+	return &capableConn{CapableConn: conn}, nil
 }
 
 func (t *WebsocketTransport) maDial(ctx context.Context, raddr ma.Multiaddr) (manet.Conn, error) {
@@ -230,5 +234,5 @@ func (t *WebsocketTransport) Listen(a ma.Multiaddr) (transport.Listener, error)
 	if err != nil {
 		return nil, err
 	}
-	return t.upgrader.UpgradeListener(t, malist), nil
+	return &transportListener{Listener: t.upgrader.UpgradeListener(t, malist)}, nil
 }
diff --git a/p2p/transport/webtransport/conn.go b/p2p/transport/webtransport/conn.go
index d8a366a0..1041b83e 100644
--- a/p2p/transport/webtransport/conn.go
+++ b/p2p/transport/webtransport/conn.go
@@ -69,9 +69,13 @@ func (c *conn) allowWindowIncrease(size uint64) bool {
 // garbage collection to properly work in this package.
 func (c *conn) Close() error {
 	c.transport.removeConn(c.session)
-	return c.session.Close()
+	return c.session.CloseWithError(0, "")
 }
 
 func (c *conn) IsClosed() bool           { return c.session.Context().Err() != nil }
 func (c *conn) Scope() network.ConnScope { return c.scope }
 func (c *conn) Transport() tpt.Transport { return c.transport }
+
+func (c *conn) ConnState() network.ConnectionState {
+	return network.ConnectionState{Transport: "webtransport"}
+}
diff --git a/p2p/transport/webtransport/listener.go b/p2p/transport/webtransport/listener.go
index 9a93d014..03ff72f8 100644
--- a/p2p/transport/webtransport/listener.go
+++ b/p2p/transport/webtransport/listener.go
@@ -137,7 +137,7 @@ func (l *listener) httpHandler(w http.ResponseWriter, r *http.Request) {
 	if err != nil {
 		cancel()
 		log.Debugw("handshake failed", "error", err)
-		sess.Close()
+		sess.CloseWithError(1, "")
 		connScope.Done()
 		return
 	}
@@ -145,14 +145,14 @@ func (l *listener) httpHandler(w http.ResponseWriter, r *http.Request) {
 
 	if l.transport.gater != nil && !l.transport.gater.InterceptSecured(network.DirInbound, sconn.RemotePeer(), sconn) {
 		// TODO: can we close with a specific error here?
-		sess.Close()
+		sess.CloseWithError(errorCodeConnectionGating, "")
 		connScope.Done()
 		return
 	}
 
 	if err := connScope.SetPeer(sconn.RemotePeer()); err != nil {
 		log.Debugw("resource manager blocked incoming connection for peer", "peer", sconn.RemotePeer(), "addr", r.RemoteAddr, "error", err)
-		sess.Close()
+		sess.CloseWithError(1, "")
 		connScope.Done()
 		return
 	}
@@ -163,7 +163,7 @@ func (l *listener) httpHandler(w http.ResponseWriter, r *http.Request) {
 	case l.queue <- conn:
 	default:
 		log.Debugw("accept queue full, dropping incoming connection", "peer", sconn.RemotePeer(), "addr", r.RemoteAddr, "error", err)
-		sess.Close()
+		sess.CloseWithError(1, "")
 		connScope.Done()
 	}
 }
diff --git a/p2p/transport/webtransport/stream.go b/p2p/transport/webtransport/stream.go
index ff17b308..623fc603 100644
--- a/p2p/transport/webtransport/stream.go
+++ b/p2p/transport/webtransport/stream.go
@@ -10,7 +10,7 @@ import (
 )
 
 const (
-	reset webtransport.ErrorCode = 0
+	reset webtransport.StreamErrorCode = 0
 )
 
 type webtransportStream struct {
diff --git a/p2p/transport/webtransport/transport.go b/p2p/transport/webtransport/transport.go
index bfb8adb3..0b133e08 100644
--- a/p2p/transport/webtransport/transport.go
+++ b/p2p/transport/webtransport/transport.go
@@ -34,6 +34,8 @@ var log = logging.Logger("webtransport")
 
 const webtransportHTTPEndpoint = "/.well-known/libp2p-webtransport"
 
+const errorCodeConnectionGating = 0x47415445 // GATE in ASCII
+
 const certValidity = 14 * 24 * time.Hour
 
 type Option func(*transport) error
@@ -153,13 +155,12 @@ func (t *transport) Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (tp
 	}
 	sconn, err := t.upgrade(ctx, sess, p, certHashes)
 	if err != nil {
-		sess.Close()
+		sess.CloseWithError(1, "")
 		scope.Done()
 		return nil, err
 	}
 	if t.gater != nil && !t.gater.InterceptSecured(network.DirOutbound, p, sconn) {
-		// TODO: can we close with a specific error here?
-		sess.Close()
+		sess.CloseWithError(errorCodeConnectionGating, "")
 		scope.Done()
 		return nil, fmt.Errorf("secured connection gated")
 	}


Test patch:
diff --git a/p2p/net/conn-security-multistream/ssms_test.go b/p2p/net/conn-security-multistream/ssms_test.go
deleted file mode 100644
index 3ccf4a7f..00000000
--- a/p2p/net/conn-security-multistream/ssms_test.go
+++ /dev/null
@@ -1,121 +0,0 @@
-package csms
-
-import (
-	"context"
-	"crypto/rand"
-	"io"
-	"net"
-	"sync"
-	"testing"
-
-	"github.com/libp2p/go-libp2p/core/crypto"
-	"github.com/libp2p/go-libp2p/core/peer"
-	"github.com/libp2p/go-libp2p/core/sec"
-	"github.com/libp2p/go-libp2p/core/sec/insecure"
-
-	"github.com/stretchr/testify/require"
-)
-
-func newPeer(t *testing.T) (crypto.PrivKey, peer.ID) {
-	priv, _, err := crypto.GenerateEd25519Key(rand.Reader)
-	require.NoError(t, err)
-	id, err := peer.IDFromPrivateKey(priv)
-	require.NoError(t, err)
-	return priv, id
-}
-
-type TransportAdapter struct {
-	mux *SSMuxer
-}
-
-func (sm *TransportAdapter) SecureInbound(ctx context.Context, insecure net.Conn, p peer.ID) (sec.SecureConn, error) {
-	sconn, _, err := sm.mux.SecureInbound(ctx, insecure, p)
-	return sconn, err
-}
-
-func (sm *TransportAdapter) SecureOutbound(ctx context.Context, insecure net.Conn, p peer.ID) (sec.SecureConn, error) {
-	sconn, _, err := sm.mux.SecureOutbound(ctx, insecure, p)
-	return sconn, err
-}
-
-func TestCommonProto(t *testing.T) {
-	privA, idA := newPeer(t)
-	privB, idB := newPeer(t)
-
-	var at, bt SSMuxer
-
-	atInsecure := insecure.NewWithIdentity(insecure.ID, idA, privA)
-	btInsecure := insecure.NewWithIdentity(insecure.ID, idB, privB)
-	at.AddTransport("/plaintext/1.0.0", atInsecure)
-	bt.AddTransport("/plaintext/1.1.0", btInsecure)
-	bt.AddTransport("/plaintext/1.0.0", btInsecure)
-
-	ln, err := net.Listen("tcp", "localhost:0")
-	require.NoError(t, err)
-	muxB := &TransportAdapter{mux: &bt}
-	connChan := make(chan sec.SecureConn)
-	go func() {
-		conn, err := ln.Accept()
-		require.NoError(t, err)
-		c, err := muxB.SecureInbound(context.Background(), conn, idA)
-		require.NoError(t, err)
-		connChan <- c
-	}()
-
-	muxA := &TransportAdapter{mux: &at}
-
-	cconn, err := net.Dial("tcp", ln.Addr().String())
-	require.NoError(t, err)
-
-	cc, err := muxA.SecureOutbound(context.Background(), cconn, idB)
-	require.NoError(t, err)
-	require.Equal(t, cc.LocalPeer(), idA)
-	require.Equal(t, cc.RemotePeer(), idB)
-	_, err = cc.Write([]byte("foobar"))
-	require.NoError(t, err)
-	require.NoError(t, cc.Close())
-
-	sc := <-connChan
-	require.Equal(t, sc.LocalPeer(), idB)
-	require.Equal(t, sc.RemotePeer(), idA)
-	b, err := io.ReadAll(sc)
-	require.NoError(t, err)
-	require.Equal(t, "foobar", string(b))
-}
-
-func TestNoCommonProto(t *testing.T) {
-	privA, idA := newPeer(t)
-	privB, idB := newPeer(t)
-
-	var at, bt SSMuxer
-	atInsecure := insecure.NewWithIdentity(insecure.ID, idA, privA)
-	btInsecure := insecure.NewWithIdentity(insecure.ID, idB, privB)
-
-	at.AddTransport("/plaintext/1.0.0", atInsecure)
-	bt.AddTransport("/plaintext/1.1.0", btInsecure)
-
-	ctx, cancel := context.WithCancel(context.Background())
-	defer cancel()
-	a, b := net.Pipe()
-
-	var wg sync.WaitGroup
-	wg.Add(2)
-	go func() {
-		defer wg.Done()
-		defer a.Close()
-		_, _, err := at.SecureInbound(ctx, a, "")
-		if err == nil {
-			t.Error("connection should have failed")
-		}
-	}()
-
-	go func() {
-		defer wg.Done()
-		defer b.Close()
-		_, _, err := bt.SecureOutbound(ctx, b, "peerA")
-		if err == nil {
-			t.Error("connection should have failed")
-		}
-	}()
-	wg.Wait()
-}
diff --git a/p2p/net/swarm/dial_worker_test.go b/p2p/net/swarm/dial_worker_test.go
index 14ff3d64..346550c1 100644
--- a/p2p/net/swarm/dial_worker_test.go
+++ b/p2p/net/swarm/dial_worker_test.go
@@ -12,11 +12,11 @@ import (
 	"github.com/libp2p/go-libp2p/core/crypto"
 	"github.com/libp2p/go-libp2p/core/peer"
 	"github.com/libp2p/go-libp2p/core/peerstore"
+	"github.com/libp2p/go-libp2p/core/sec"
 	"github.com/libp2p/go-libp2p/core/sec/insecure"
 	"github.com/libp2p/go-libp2p/core/transport"
 	"github.com/libp2p/go-libp2p/p2p/host/peerstore/pstoremem"
 	"github.com/libp2p/go-libp2p/p2p/muxer/yamux"
-	csms "github.com/libp2p/go-libp2p/p2p/net/conn-security-multistream"
 	tptu "github.com/libp2p/go-libp2p/p2p/net/upgrader"
 	quic "github.com/libp2p/go-libp2p/p2p/transport/quic"
 	"github.com/libp2p/go-libp2p/p2p/transport/tcp"
@@ -75,10 +75,9 @@ func makeSwarm(t *testing.T) *Swarm {
 func makeUpgrader(t *testing.T, n *Swarm) transport.Upgrader {
 	id := n.LocalPeer()
 	pk := n.Peerstore().PrivKey(id)
-	secMuxer := new(csms.SSMuxer)
-	secMuxer.AddTransport(insecure.ID, insecure.NewWithIdentity(insecure.ID, id, pk))
+	st := insecure.NewWithIdentity(insecure.ID, id, pk)
 
-	u, err := tptu.New(secMuxer, []tptu.StreamMuxer{{ID: "/yamux/1.0.0", Muxer: yamux.DefaultTransport}}, nil, nil, nil)
+	u, err := tptu.New([]sec.SecureTransport{st}, []tptu.StreamMuxer{{ID: "/yamux/1.0.0", Muxer: yamux.DefaultTransport}}, nil, nil, nil)
 	require.NoError(t, err)
 	return u
 }
diff --git a/p2p/net/swarm/testing/testing.go b/p2p/net/swarm/testing/testing.go
index 8b9420c4..08fe7ad0 100644
--- a/p2p/net/swarm/testing/testing.go
+++ b/p2p/net/swarm/testing/testing.go
@@ -12,11 +12,11 @@ import (
 	"github.com/libp2p/go-libp2p/core/network"
 	"github.com/libp2p/go-libp2p/core/peer"
 	"github.com/libp2p/go-libp2p/core/peerstore"
+	"github.com/libp2p/go-libp2p/core/sec"
 	"github.com/libp2p/go-libp2p/core/sec/insecure"
 	"github.com/libp2p/go-libp2p/core/transport"
 	"github.com/libp2p/go-libp2p/p2p/host/peerstore/pstoremem"
 	"github.com/libp2p/go-libp2p/p2p/muxer/yamux"
-	csms "github.com/libp2p/go-libp2p/p2p/net/conn-security-multistream"
 	"github.com/libp2p/go-libp2p/p2p/net/swarm"
 	tptu "github.com/libp2p/go-libp2p/p2p/net/upgrader"
 	quic "github.com/libp2p/go-libp2p/p2p/transport/quic"
@@ -101,10 +101,9 @@ func OptPeerPrivateKey(sk crypto.PrivKey) Option {
 func GenUpgrader(t *testing.T, n *swarm.Swarm, connGater connmgr.ConnectionGater, opts ...tptu.Option) transport.Upgrader {
 	id := n.LocalPeer()
 	pk := n.Peerstore().PrivKey(id)
-	secMuxer := new(csms.SSMuxer)
-	secMuxer.AddTransport(insecure.ID, insecure.NewWithIdentity(insecure.ID, id, pk))
+	st := insecure.NewWithIdentity(insecure.ID, id, pk)
 
-	u, err := tptu.New(secMuxer, []tptu.StreamMuxer{{ID: "/yamux/1.0.0", Muxer: yamux.DefaultTransport}}, nil, nil, connGater, opts...)
+	u, err := tptu.New([]sec.SecureTransport{st}, []tptu.StreamMuxer{{ID: "/yamux/1.0.0", Muxer: yamux.DefaultTransport}}, nil, nil, connGater, opts...)
 	require.NoError(t, err)
 	return u
 }
diff --git a/p2p/net/upgrader/listener_test.go b/p2p/net/upgrader/listener_test.go
index 0d872d1d..26b13f1b 100644
--- a/p2p/net/upgrader/listener_test.go
+++ b/p2p/net/upgrader/listener_test.go
@@ -4,7 +4,6 @@ import (
 	"context"
 	"errors"
 	"io"
-	"net"
 	"os"
 	"sync"
 	"sync/atomic"
@@ -15,6 +14,7 @@ import (
 	mocknetwork "github.com/libp2p/go-libp2p/core/network/mocks"
 	"github.com/libp2p/go-libp2p/core/peer"
 	"github.com/libp2p/go-libp2p/core/sec"
+	"github.com/libp2p/go-libp2p/core/sec/insecure"
 	"github.com/libp2p/go-libp2p/core/transport"
 	"github.com/libp2p/go-libp2p/p2p/net/upgrader"
 
@@ -24,22 +24,6 @@ import (
 	"github.com/stretchr/testify/require"
 )
 
-type MuxAdapter struct {
-	tpt sec.SecureTransport
-}
-
-var _ sec.SecureMuxer = &MuxAdapter{}
-
-func (mux *MuxAdapter) SecureInbound(ctx context.Context, insecure net.Conn, p peer.ID) (sec.SecureConn, bool, error) {
-	sconn, err := mux.tpt.SecureInbound(ctx, insecure, p)
-	return sconn, true, err
-}
-
-func (mux *MuxAdapter) SecureOutbound(ctx context.Context, insecure net.Conn, p peer.ID) (sec.SecureConn, bool, error) {
-	sconn, err := mux.tpt.SecureOutbound(ctx, insecure, p)
-	return sconn, false, err
-}
-
 func createListener(t *testing.T, u transport.Upgrader) transport.Listener {
 	t.Helper()
 	addr, err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/0")
@@ -401,3 +385,33 @@ func TestListenerResourceManagementDenied(t *testing.T) {
 	require.NoError(t, ln.Close())
 	<-done
 }
+
+func TestNoCommonSecurityProto(t *testing.T) {
+	idA, privA := newPeer(t)
+	idB, privB := newPeer(t)
+	atInsecure := insecure.NewWithIdentity("/plaintext1", idA, privA)
+	btInsecure := insecure.NewWithIdentity("/plaintext2", idB, privB)
+
+	ua, err := upgrader.New([]sec.SecureTransport{atInsecure}, []upgrader.StreamMuxer{{ID: "negotiate", Muxer: &negotiatingMuxer{}}}, nil, nil, nil)
+	require.NoError(t, err)
+	ub, err := upgrader.New([]sec.SecureTransport{btInsecure}, []upgrader.StreamMuxer{{ID: "negotiate", Muxer: &negotiatingMuxer{}}}, nil, nil, nil)
+	require.NoError(t, err)
+
+	ln := createListener(t, ua)
+	done := make(chan struct{})
+	go func() {
+		defer close(done)
+		ln.Accept()
+	}()
+
+	_, err = dial(t, ub, ln.Multiaddrs()[0], idA, &network.NullScope{})
+	require.EqualError(t, err, "failed to negotiate security protocol: protocol not supported")
+	select {
+	case <-done:
+		t.Fatal("didn't expect to accept a connection")
+	case <-time.After(50 * time.Millisecond):
+	}
+
+	ln.Close()
+	<-done
+}
diff --git a/p2p/net/upgrader/upgrader_test.go b/p2p/net/upgrader/upgrader_test.go
index ccbd6ffa..fbe9527a 100644
--- a/p2p/net/upgrader/upgrader_test.go
+++ b/p2p/net/upgrader/upgrader_test.go
@@ -2,6 +2,7 @@ package upgrader_test
 
 import (
 	"context"
+	"crypto/rand"
 	"errors"
 	"net"
 	"testing"
@@ -11,8 +12,8 @@ import (
 	"github.com/libp2p/go-libp2p/core/network"
 	mocknetwork "github.com/libp2p/go-libp2p/core/network/mocks"
 	"github.com/libp2p/go-libp2p/core/peer"
+	"github.com/libp2p/go-libp2p/core/sec"
 	"github.com/libp2p/go-libp2p/core/sec/insecure"
-	"github.com/libp2p/go-libp2p/core/test"
 	"github.com/libp2p/go-libp2p/core/transport"
 	"github.com/libp2p/go-libp2p/p2p/muxer/yamux"
 	"github.com/libp2p/go-libp2p/p2p/net/upgrader"
@@ -39,12 +40,18 @@ func createUpgraderWithOpts(t *testing.T, opts ...upgrader.Option) (peer.ID, tra
 	return createUpgraderWithMuxers(t, []upgrader.StreamMuxer{{ID: "negotiate", Muxer: &negotiatingMuxer{}}}, nil, nil, opts...)
 }
 
-func createUpgraderWithMuxers(t *testing.T, muxers []upgrader.StreamMuxer, rcmgr network.ResourceManager, connGater connmgr.ConnectionGater, opts ...upgrader.Option) (peer.ID, transport.Upgrader) {
-	priv, _, err := test.RandTestKeyPair(crypto.Ed25519, 256)
+func newPeer(t *testing.T) (peer.ID, crypto.PrivKey) {
+	t.Helper()
+	priv, _, err := crypto.GenerateEd25519Key(rand.Reader)
 	require.NoError(t, err)
 	id, err := peer.IDFromPrivateKey(priv)
 	require.NoError(t, err)
-	u, err := upgrader.New(&MuxAdapter{tpt: insecure.NewWithIdentity(insecure.ID, id, priv)}, muxers, nil, rcmgr, connGater, opts...)
+	return id, priv
+}
+
+func createUpgraderWithMuxers(t *testing.T, muxers []upgrader.StreamMuxer, rcmgr network.ResourceManager, connGater connmgr.ConnectionGater, opts ...upgrader.Option) (peer.ID, transport.Upgrader) {
+	id, priv := newPeer(t)
+	u, err := upgrader.New([]sec.SecureTransport{insecure.NewWithIdentity(insecure.ID, id, priv)}, muxers, nil, rcmgr, connGater, opts...)
 	require.NoError(t, err)
 	return id, u
 }
@@ -194,8 +201,4 @@ func TestOutboundResourceManagement(t *testing.T) {
 		_, err := dial(t, dialUpgrader, ln.Multiaddrs()[0], id, connScope)
 		require.Error(t, err)
 	})
-
-	t.Run("blocked by the resource manager", func(t *testing.T) {
-
-	})
 }
diff --git a/p2p/protocol/circuitv2/client/reservation_test.go b/p2p/protocol/circuitv2/client/reservation_test.go
index 02a2f858..15b0dd9d 100644
--- a/p2p/protocol/circuitv2/client/reservation_test.go
+++ b/p2p/protocol/circuitv2/client/reservation_test.go
@@ -82,7 +82,7 @@ func TestReservationFailures(t *testing.T) {
 
 	for _, tc := range testcases {
 		t.Run(tc.name, func(t *testing.T) {
-			host, err := libp2p.New()
+			host, err := libp2p.New(libp2p.ResourceManager(&network.NullResourceManager{}))
 			require.NoError(t, err)
 			defer host.Close()
 			if tc.streamHandler != nil {
diff --git a/p2p/protocol/holepunch/holepunch_test.go b/p2p/protocol/holepunch/holepunch_test.go
index 002ae5cd..0b37cbf1 100644
--- a/p2p/protocol/holepunch/holepunch_test.go
+++ b/p2p/protocol/holepunch/holepunch_test.go
@@ -424,6 +424,7 @@ func mkHostWithStaticAutoRelay(t *testing.T, relay host.Host) host.Host {
 			autorelay.WithStaticRelays([]peer.AddrInfo{pi}),
 		),
 		libp2p.ForceReachabilityPrivate(),
+		libp2p.ResourceManager(&network.NullResourceManager{}),
 	)
 	require.NoError(t, err)
 
@@ -443,7 +444,11 @@ func makeRelayedHosts(t *testing.T, h1opt, h2opt []holepunch.Option, addHolePunc
 	t.Helper()
 	h1, _ = mkHostWithHolePunchSvc(t, h1opt...)
 	var err error
-	relay, err = libp2p.New(libp2p.ListenAddrs(ma.StringCast("/ip4/127.0.0.1/tcp/0")), libp2p.DisableRelay())
+	relay, err = libp2p.New(
+		libp2p.ListenAddrs(ma.StringCast("/ip4/127.0.0.1/tcp/0")),
+		libp2p.DisableRelay(),
+		libp2p.ResourceManager(&network.NullResourceManager{}),
+	)
 	require.NoError(t, err)
 
 	_, err = relayv1.NewRelay(relay)
@@ -483,6 +488,7 @@ func mkHostWithHolePunchSvc(t *testing.T, opts ...holepunch.Option) (host.Host,
 	h, err := libp2p.New(
 		libp2p.ListenAddrs(ma.StringCast("/ip4/127.0.0.1/tcp/0"), ma.StringCast("/ip6/::1/tcp/0")),
 		libp2p.ForceReachabilityPrivate(),
+		libp2p.ResourceManager(&network.NullResourceManager{}),
 	)
 	require.NoError(t, err)
 	hps, err := holepunch.NewService(h, newMockIDService(t, h), opts...)
diff --git a/p2p/security/noise/transport_test.go b/p2p/security/noise/transport_test.go
index 056afa39..0912dab4 100644
--- a/p2p/security/noise/transport_test.go
+++ b/p2p/security/noise/transport_test.go
@@ -689,8 +689,8 @@ func TestHandshakeWithTransportEarlyData(t *testing.T) {
 		defer initConn.Close()
 		defer respConn.Close()
 
-		require.Equal(t, expectedProto, initConn.connectionState.NextProto)
-		require.Equal(t, expectedProto, respConn.connectionState.NextProto)
+		require.Equal(t, expectedProto, initConn.connectionState.StreamMultiplexer)
+		require.Equal(t, expectedProto, respConn.connectionState.StreamMultiplexer)
 
 		initData := []byte("Test data for noise transport")
 		_, err := initConn.Write(initData)
diff --git a/p2p/security/tls/transport_test.go b/p2p/security/tls/transport_test.go
index 0d42eb1e..0f5247fb 100644
--- a/p2p/security/tls/transport_test.go
+++ b/p2p/security/tls/transport_test.go
@@ -255,7 +255,7 @@ func TestHandshakeWithNextProtoSucceeds(t *testing.T) {
 		require.Equal(t, serverConn.RemotePeer(), clientID)
 		require.True(t, clientConn.RemotePublicKey().Equals(serverKey.GetPublic()), "server public key mismatch")
 		require.True(t, serverConn.RemotePublicKey().Equals(clientKey.GetPublic()), "client public key mismatch")
-		require.Equal(t, clientConn.ConnState().NextProto, expectedMuxer)
+		require.Equal(t, clientConn.ConnState().StreamMultiplexer, expectedMuxer)
 		// exchange some data
 		_, err = serverConn.Write([]byte("foobar"))
 		require.NoError(t, err)
@@ -265,7 +265,7 @@ func TestHandshakeWithNextProtoSucceeds(t *testing.T) {
 		require.Equal(t, string(b), "foobar")
 	}
 
-	// Iterate through the NextProto combinations.
+	// Iterate through the StreamMultiplexer combinations.
 	for _, test := range tests {
 		clientMuxers := make([]tptu.StreamMuxer, 0, len(test.clientProtos))
 		for _, id := range test.clientProtos {
@@ -324,7 +324,7 @@ func TestHandshakeConnectionCancellations(t *testing.T) {
 		}()
 		ctx, cancel := context.WithCancel(context.Background())
 		cancel()
-		_, err = clientTransport.SecureOutbound(ctx, clientInsecureConn, serverID)
+		_, err = clientTransport.SecureOutbound(ctx, &delayedConn{Conn: clientInsecureConn, delay: 5 * time.Millisecond}, serverID)
 		require.ErrorIs(t, err, context.Canceled)
 		require.Error(t, <-errChan)
 	})
diff --git a/p2p/test/muxer-negotiation/muxer_test.go b/p2p/test/negotiation/muxer_test.go
similarity index 98%
rename from p2p/test/muxer-negotiation/muxer_test.go
rename to p2p/test/negotiation/muxer_test.go
index a36bfbde..9fc13fd3 100644
--- a/p2p/test/muxer-negotiation/muxer_test.go
+++ b/p2p/test/negotiation/muxer_test.go
@@ -1,4 +1,4 @@
-package muxer_negotiation
+package negotiation
 
 import (
 	"context"
@@ -119,7 +119,7 @@ func TestMuxerNegotiation(t *testing.T) {
 				require.NoError(t, err)
 				conns := client.Network().ConnsToPeer(server.ID())
 				require.Len(t, conns, 1, "expected exactly one connection")
-				require.Equal(t, tc.Expected, protocol.ID(conns[0].ConnState().NextProto))
+				require.Equal(t, tc.Expected, protocol.ID(conns[0].ConnState().StreamMultiplexer))
 			})
 		}
 	}
diff --git a/p2p/test/negotiation/security_test.go b/p2p/test/negotiation/security_test.go
new file mode 100644
index 00000000..3c309e24
--- /dev/null
+++ b/p2p/test/negotiation/security_test.go
@@ -0,0 +1,90 @@
+package negotiation
+
+import (
+	"context"
+	"crypto/rand"
+	"testing"
+
+	"github.com/libp2p/go-libp2p"
+	"github.com/libp2p/go-libp2p/core/crypto"
+	"github.com/libp2p/go-libp2p/core/peer"
+	"github.com/libp2p/go-libp2p/core/protocol"
+	"github.com/libp2p/go-libp2p/p2p/security/noise"
+	tls "github.com/libp2p/go-libp2p/p2p/security/tls"
+	"github.com/libp2p/go-libp2p/p2p/transport/tcp"
+
+	"github.com/stretchr/testify/require"
+)
+
+var (
+	noiseOpt = libp2p.Security("/noise", noise.New)
+	tlsOpt   = libp2p.Security("/tls", tls.New)
+)
+
+func TestSecurityNegotiation(t *testing.T) {
+	testcases := []testcase{
+		{
+			Name:             "server and client have the same preference",
+			ServerPreference: []libp2p.Option{tlsOpt, noiseOpt},
+			ClientPreference: []libp2p.Option{tlsOpt, noiseOpt},
+			Expected:         "/tls",
+		},
+		{
+			Name:             "client only supports one security",
+			ServerPreference: []libp2p.Option{tlsOpt, noiseOpt},
+			ClientPreference: []libp2p.Option{noiseOpt},
+			Expected:         "/noise",
+		},
+		{
+			Name:             "server only supports one security",
+			ServerPreference: []libp2p.Option{noiseOpt},
+			ClientPreference: []libp2p.Option{tlsOpt, noiseOpt},
+			Expected:         "/noise",
+		},
+		{
+			Name:             "no  overlap",
+			ServerPreference: []libp2p.Option{noiseOpt},
+			ClientPreference: []libp2p.Option{tlsOpt},
+			Error:            "failed to negotiate security protocol: protocol not supported",
+		},
+	}
+
+	clientID, _, err := crypto.GenerateEd25519Key(rand.Reader)
+	require.NoError(t, err)
+	serverID, _, err := crypto.GenerateEd25519Key(rand.Reader)
+	require.NoError(t, err)
+
+	for _, tc := range testcases {
+		tc := tc
+
+		t.Run(tc.Name, func(t *testing.T) {
+			server, err := libp2p.New(
+				libp2p.Identity(serverID),
+				libp2p.ChainOptions(tc.ServerPreference...),
+				libp2p.Transport(tcp.NewTCPTransport),
+				libp2p.ListenAddrStrings("/ip4/127.0.0.1/tcp/0"),
+			)
+			require.NoError(t, err)
+
+			client, err := libp2p.New(
+				libp2p.Identity(clientID),
+				libp2p.ChainOptions(tc.ClientPreference...),
+				libp2p.Transport(tcp.NewTCPTransport),
+				libp2p.NoListenAddrs,
+			)
+			require.NoError(t, err)
+
+			err = client.Connect(context.Background(), peer.AddrInfo{ID: server.ID(), Addrs: server.Addrs()})
+			if tc.Error != "" {
+				require.Error(t, err)
+				require.ErrorContains(t, err, tc.Error)
+				return
+			}
+
+			require.NoError(t, err)
+			conns := client.Network().ConnsToPeer(server.ID())
+			require.Len(t, conns, 1, "expected exactly one connection")
+			require.Equal(t, tc.Expected, protocol.ID(conns[0].ConnState().Security))
+		})
+	}
+}
diff --git a/p2p/transport/tcp/tcp_test.go b/p2p/transport/tcp/tcp_test.go
index b320c5a4..1dacc719 100644
--- a/p2p/transport/tcp/tcp_test.go
+++ b/p2p/transport/tcp/tcp_test.go
@@ -13,7 +13,6 @@ import (
 	"github.com/libp2p/go-libp2p/core/sec/insecure"
 	"github.com/libp2p/go-libp2p/core/transport"
 	"github.com/libp2p/go-libp2p/p2p/muxer/yamux"
-	csms "github.com/libp2p/go-libp2p/p2p/net/conn-security-multistream"
 	tptu "github.com/libp2p/go-libp2p/p2p/net/upgrader"
 	ttransport "github.com/libp2p/go-libp2p/p2p/transport/testsuite"
 
@@ -148,13 +147,11 @@ func TestTcpTransportCantListenUtp(t *testing.T) {
 	envReuseportVal = true
 }
 
-func makeInsecureMuxer(t *testing.T) (peer.ID, sec.SecureMuxer) {
+func makeInsecureMuxer(t *testing.T) (peer.ID, []sec.SecureTransport) {
 	t.Helper()
 	priv, _, err := crypto.GenerateKeyPair(crypto.Ed25519, 256)
 	require.NoError(t, err)
 	id, err := peer.IDFromPrivateKey(priv)
 	require.NoError(t, err)
-	var secMuxer csms.SSMuxer
-	secMuxer.AddTransport(insecure.ID, insecure.NewWithIdentity(insecure.ID, id, priv))
-	return id, &secMuxer
+	return id, []sec.SecureTransport{insecure.NewWithIdentity(insecure.ID, id, priv)}
 }
diff --git a/p2p/transport/websocket/websocket_test.go b/p2p/transport/websocket/websocket_test.go
index 051c8064..0b05f942 100644
--- a/p2p/transport/websocket/websocket_test.go
+++ b/p2p/transport/websocket/websocket_test.go
@@ -27,7 +27,6 @@ import (
 	"github.com/libp2p/go-libp2p/core/test"
 	"github.com/libp2p/go-libp2p/core/transport"
 	"github.com/libp2p/go-libp2p/p2p/muxer/yamux"
-	csms "github.com/libp2p/go-libp2p/p2p/net/conn-security-multistream"
 	tptu "github.com/libp2p/go-libp2p/p2p/net/upgrader"
 	"github.com/libp2p/go-libp2p/p2p/security/noise"
 	ttransport "github.com/libp2p/go-libp2p/p2p/transport/testsuite"
@@ -56,22 +55,16 @@ func newSecureUpgrader(t *testing.T) (peer.ID, transport.Upgrader) {
 	return id, u
 }
 
-func newInsecureMuxer(t *testing.T) (peer.ID, sec.SecureMuxer) {
+func newInsecureMuxer(t *testing.T) (peer.ID, []sec.SecureTransport) {
 	t.Helper()
 	priv, _, err := test.RandTestKeyPair(crypto.Ed25519, 256)
-	if err != nil {
-		t.Fatal(err)
-	}
+	require.NoError(t, err)
 	id, err := peer.IDFromPrivateKey(priv)
-	if err != nil {
-		t.Fatal(err)
-	}
-	var secMuxer csms.SSMuxer
-	secMuxer.AddTransport(insecure.ID, insecure.NewWithIdentity(insecure.ID, id, priv))
-	return id, &secMuxer
+	require.NoError(t, err)
+	return id, []sec.SecureTransport{insecure.NewWithIdentity(insecure.ID, id, priv)}
 }
 
-func newSecureMuxer(t *testing.T) (peer.ID, sec.SecureMuxer) {
+func newSecureMuxer(t *testing.T) (peer.ID, []sec.SecureTransport) {
 	t.Helper()
 	priv, _, err := test.RandTestKeyPair(crypto.Ed25519, 256)
 	if err != nil {
@@ -81,11 +74,9 @@ func newSecureMuxer(t *testing.T) (peer.ID, sec.SecureMuxer) {
 	if err != nil {
 		t.Fatal(err)
 	}
-	var secMuxer csms.SSMuxer
 	noiseTpt, err := noise.New(noise.ID, priv, nil)
 	require.NoError(t, err)
-	secMuxer.AddTransport(noise.ID, noiseTpt)
-	return id, &secMuxer
+	return id, []sec.SecureTransport{noiseTpt}
 }
 
 func lastComponent(t *testing.T, a ma.Multiaddr) ma.Multiaddr {