Skip to content

Commit e0874d2

Browse files
committed
feat(network): Add Conn.As
ConnAs works in a similar way to errors.As. It allows a user to cut through the interface layers and extract a specific type of connection if available. This serves as a sort of escape hatch to allow users to leverage some connection specific feature without having to support that feature for all connections. Getting RTT information is one example. It also allows us, within the library, to get specific types of connections out of the interface box. This would have been useful in the recent changes in tcpreuse. See #3181 and #3142. Getting access to the underlying type can lead to hard to debug issues. For example, if a user mutates connection state on the underlying type, hooks that relied on only mutating that state from the wrapped connection would never be called. It is up to the user to ensure they are using this safely.
1 parent fc4a618 commit e0874d2

File tree

12 files changed

+203
-0
lines changed

12 files changed

+203
-0
lines changed

core/network/conn.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,20 @@ type Conn interface {
8787
// IsClosed returns whether a connection is fully closed, so it can
8888
// be garbage collected.
8989
IsClosed() bool
90+
91+
// As finds the first conn in Conn's wrapped types that matches target, and
92+
// if one is found, sets target to that conn value and returns true.
93+
// Otherwise, it returns false. Similar to errors.As.
94+
//
95+
// target must be a pointer to the type you are matching against.
96+
//
97+
// This is an EXPERIMENTAL API. Getting access to the underlying type can
98+
// lead to hard to debug issues. For example, if you mutate connection state
99+
// on the underlying type, hooks that relied on only mutating that state
100+
// from the wrapped connection would never be called.
101+
//
102+
// You very likely do not need to use this method.
103+
As(target any) bool
90104
}
91105

92106
// ConnectionState holds information about the connection.

core/network/mux.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,20 @@ type MuxedConn interface {
137137

138138
// AcceptStream accepts a stream opened by the other side.
139139
AcceptStream() (MuxedStream, error)
140+
141+
// As finds the first conn in MuxedConn's wrapped types that matches target,
142+
// and if one is found, sets target to that conn value and returns true.
143+
// Otherwise, it returns false. Similar to errors.As.
144+
//
145+
// target must be a pointer to the type you are matching against.
146+
//
147+
// This is an EXPERIMENTAL API. Getting access to the underlying type can
148+
// lead to hard to debug issues. For example, if you mutate connection state
149+
// on the underlying type, hooks that relied on only mutating that state
150+
// from the wrapped connection would never be called.
151+
//
152+
// You very likely do not need to use this method.
153+
As(target any) bool
140154
}
141155

142156
// Multiplexer wraps a net.Conn with a stream multiplexing

libp2p_test.go

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ import (
4040
libp2pwebrtc "github.com/libp2p/go-libp2p/p2p/transport/webrtc"
4141
"github.com/libp2p/go-libp2p/p2p/transport/websocket"
4242
webtransport "github.com/libp2p/go-libp2p/p2p/transport/webtransport"
43+
"github.com/libp2p/go-yamux/v5"
44+
"github.com/pion/webrtc/v4"
45+
quicgo "github.com/quic-go/quic-go"
46+
wtgo "github.com/quic-go/webtransport-go"
4347
"go.uber.org/goleak"
4448

4549
ma "github.com/multiformats/go-multiaddr"
@@ -842,3 +846,120 @@ func BenchmarkAllAddrs(b *testing.B) {
842846
addrsHost.AllAddrs()
843847
}
844848
}
849+
850+
func TestConnAs(t *testing.T) {
851+
// t.Run("quic conns", func(t *testing.T) {
852+
// h1, err := New(ListenAddrStrings(
853+
// "/ip4/0.0.0.0/udp/0/quic-v1",
854+
// ))
855+
// require.NoError(t, err)
856+
// defer h1.Close()
857+
// h2, err := New(ListenAddrStrings(
858+
// "/ip4/0.0.0.0/udp/0/quic-v1",
859+
// ))
860+
// require.NoError(t, err)
861+
// defer h2.Close()
862+
// err = h1.Connect(context.Background(), peer.AddrInfo{
863+
// ID: h2.ID(),
864+
// Addrs: h2.Addrs(),
865+
// })
866+
// require.NoError(t, err)
867+
// c := h1.Network().ConnsToPeer(h2.ID())[0]
868+
869+
// var quicConn *quicgo.Conn
870+
// require.True(t, c.As(&quicConn))
871+
// })
872+
873+
// t.Run("TCP conn with metrics", func(t *testing.T) {
874+
// h1, err := New(ListenAddrStrings(
875+
// "/ip4/0.0.0.0/tcp/0",
876+
// ), Transport(tcp.NewTCPTransport, tcp.WithMetrics()))
877+
// require.NoError(t, err)
878+
// defer h1.Close()
879+
// h2, err := New(ListenAddrStrings(
880+
// "/ip4/0.0.0.0/tcp/0",
881+
// ), Transport(tcp.NewTCPTransport, tcp.WithMetrics()))
882+
// require.NoError(t, err)
883+
// defer h2.Close()
884+
// err = h1.Connect(context.Background(), peer.AddrInfo{
885+
// ID: h2.ID(),
886+
// Addrs: h2.Addrs(),
887+
// })
888+
// require.NoError(t, err)
889+
// c := h1.Network().ConnsToPeer(h2.ID())[0]
890+
891+
// var yamuxSession *yamux.Session
892+
// require.True(t, c.As(&yamuxSession))
893+
// })
894+
895+
type testCase struct {
896+
name string
897+
listenAddr string
898+
testAs func(t *testing.T, c network.Conn)
899+
}
900+
901+
testCases := []testCase{
902+
{
903+
"QUIC",
904+
"/ip4/0.0.0.0/udp/0/quic-v1",
905+
func(t *testing.T, c network.Conn) {
906+
var quicConn *quicgo.Conn
907+
require.True(t, c.As(&quicConn))
908+
},
909+
},
910+
{
911+
"TCP+Yamux",
912+
"/ip4/0.0.0.0/tcp/0",
913+
func(t *testing.T, c network.Conn) {
914+
var yamuxSession *yamux.Session
915+
require.True(t, c.As(&yamuxSession))
916+
},
917+
},
918+
{
919+
"WebRTC",
920+
"/ip4/0.0.0.0/udp/0/webrtc-direct",
921+
func(t *testing.T, c network.Conn) {
922+
var webrtcPC *webrtc.PeerConnection
923+
require.True(t, c.As(&webrtcPC))
924+
},
925+
},
926+
{
927+
"WebTransport Session",
928+
"/ip4/0.0.0.0/udp/0/quic-v1/webtransport",
929+
func(t *testing.T, c network.Conn) {
930+
var s *wtgo.Session
931+
require.True(t, c.As(&s))
932+
},
933+
},
934+
{
935+
"WebTransport QUIC Conn",
936+
"/ip4/0.0.0.0/udp/0/quic-v1/webtransport",
937+
func(t *testing.T, c network.Conn) {
938+
var quicConn *quicgo.Conn
939+
require.True(t, c.As(&quicConn))
940+
},
941+
},
942+
}
943+
944+
for _, tc := range testCases {
945+
t.Run(tc.name, func(t *testing.T) {
946+
h1, err := New(ListenAddrStrings(
947+
tc.listenAddr,
948+
))
949+
require.NoError(t, err)
950+
defer h1.Close()
951+
h2, err := New(ListenAddrStrings(
952+
tc.listenAddr,
953+
))
954+
require.NoError(t, err)
955+
defer h2.Close()
956+
err = h1.Connect(context.Background(), peer.AddrInfo{
957+
ID: h2.ID(),
958+
Addrs: h2.Addrs(),
959+
})
960+
require.NoError(t, err)
961+
c := h1.Network().ConnsToPeer(h2.ID())[0]
962+
tc.testAs(t, c)
963+
})
964+
}
965+
}

p2p/muxer/yamux/conn.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,14 @@ type conn yamux.Session
1313

1414
var _ network.MuxedConn = &conn{}
1515

16+
func (c *conn) As(target any) bool {
17+
if t, ok := target.(**yamux.Session); ok {
18+
*t = (*yamux.Session)(c)
19+
return true
20+
}
21+
return false
22+
}
23+
1624
// NewMuxedConn constructs a new MuxedConn from a yamux.Session.
1725
func NewMuxedConn(m *yamux.Session) network.MuxedConn {
1826
return (*conn)(m)

p2p/net/connmgr/connmgr_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -818,6 +818,7 @@ func (m mockConn) NewStream(_ context.Context) (network.Stream, error) { panic("
818818
func (m mockConn) GetStreams() []network.Stream { panic("implement me") }
819819
func (m mockConn) Scope() network.ConnScope { panic("implement me") }
820820
func (m mockConn) ConnState() network.ConnectionState { return network.ConnectionState{} }
821+
func (m mockConn) As(target any) bool { return false }
821822

822823
func makeSegmentsWithPeerInfos(peerInfos peerInfos) *segments {
823824
var s = func() *segments {

p2p/net/mock/mock_conn.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,10 @@ func (c *conn) Close() error {
8686
return nil
8787
}
8888

89+
func (c *conn) As(target any) bool {
90+
return false
91+
}
92+
8993
func (c *conn) teardown() {
9094
for _, s := range c.allStreams() {
9195
s.Reset()

p2p/net/swarm/swarm.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -833,6 +833,10 @@ func wrapWithMetrics(capableConn transport.CapableConn, metricsTracer MetricsTra
833833
return c
834834
}
835835

836+
func (c *connWithMetrics) As(target any) bool {
837+
return c.CapableConn.As(target)
838+
}
839+
836840
func (c *connWithMetrics) completedHandshake() {
837841
c.metricsTracer.CompletedHandshake(time.Since(c.opened), c.ConnState(), c.LocalMultiaddr())
838842
}

p2p/net/swarm/swarm_conn.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ type Conn struct {
4242

4343
var _ network.Conn = &Conn{}
4444

45+
func (c *Conn) As(target any) bool {
46+
return c.conn.As(target)
47+
}
48+
4549
func (c *Conn) IsClosed() bool {
4650
return c.conn.IsClosed()
4751
}

p2p/net/upgrader/conn.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ type transportConn struct {
2323

2424
var _ transport.CapableConn = &transportConn{}
2525

26+
func (c *transportConn) As(target any) bool {
27+
return c.MuxedConn.As(target)
28+
}
29+
2630
func (t *transportConn) Transport() transport.Transport {
2731
return t.transport
2832
}

p2p/transport/quic/conn.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,15 @@ type conn struct {
2525
remoteMultiaddr ma.Multiaddr
2626
}
2727

28+
func (c *conn) As(target any) bool {
29+
if t, ok := target.(**quic.Conn); ok {
30+
*t = c.quicConn
31+
return true
32+
}
33+
34+
return false
35+
}
36+
2837
var _ tpt.CapableConn = &conn{}
2938

3039
// Close closes the connection.

0 commit comments

Comments
 (0)