Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions p2p/host/basic/addrs_manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,40 @@ func TestAddrsManagerReachabilityEvent(t *testing.T) {
}
}

func TestAddrsManagerConfirmedAddrsIncludesSecondaryTransports(t *testing.T) {
// A node listening on every transport kubo enables by default. The
// secondary transports (ws, webrtc-direct, webtransport) inherit Public
// from their thin-waist primary, and all of them must survive into
// ConfirmedAddrs: getConfirmedAddrs feeds these slices to
// removeNotInSource, which drops entries when its input is not sorted.
tcp := ma.StringCast("/ip4/1.2.3.4/tcp/4001")
wsSNI := ma.StringCast("/ip4/1.2.3.4/tcp/4001/tls/sni/*.example.net/ws")
quic := ma.StringCast("/ip4/1.2.3.4/udp/4001/quic-v1")
webrtc := ma.StringCast("/ip4/1.2.3.4/udp/4001/webrtc-direct")
wt := ma.StringCast("/ip4/1.2.3.4/udp/4001/quic-v1/webtransport")
listenAddrs := []ma.Multiaddr{tcp, wsSNI, quic, webrtc, wt}

am := newAddrsManagerTestCase(t, addrsManagerArgs{
ListenAddrs: func() []ma.Multiaddr { return listenAddrs },
AutoNATClient: mockAutoNATClient{
F: func(_ context.Context, reqs []autonatv2.Request) (autonatv2.Result, error) {
return autonatv2.Result{Addr: reqs[0].Addr, Idx: 0, Reachability: network.ReachabilityPublic}, nil
},
},
})
defer am.Close()

require.Eventually(t, func() bool {
reachable, _, _ := am.ConfirmedAddrs()
return len(reachable) == len(listenAddrs)
}, 5*time.Second, 50*time.Millisecond, "expected all listen addrs to become confirmed reachable")

reachable, unreachable, unknown := am.ConfirmedAddrs()
matest.AssertMultiaddrsMatch(t, listenAddrs, reachable)
require.Empty(t, unreachable)
require.Empty(t, unknown)
}

func TestAddrsManagerPeerstoreUpdated(t *testing.T) {
quic1 := ma.StringCast("/ip4/1.2.3.4/udp/1234/quic-v1")
quic2 := ma.StringCast("/ip4/1.2.3.5/udp/1/quic-v1")
Expand Down
13 changes: 13 additions & 0 deletions p2p/host/basic/addrs_reachability_tracker.go
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,9 @@ func newProbeManager(now func() time.Time) *probeManager {
}

// AppendConfirmedAddrs appends the current confirmed reachable and unreachable addresses.
// The returned slices are sorted by Multiaddr.Compare: addrsManager.getConfirmedAddrs
// passes them to removeNotInSource, and Addrs passes the unreachable set to
// removeInSource, both of which require sorted input.
func (m *probeManager) AppendConfirmedAddrs(reachable, unreachable, unknown []ma.Multiaddr) (reachableAddrs, unreachableAddrs, unknownAddrs []ma.Multiaddr) {
m.mx.Lock()
defer m.mx.Unlock()
Expand Down Expand Up @@ -425,6 +428,16 @@ func (m *probeManager) AppendConfirmedAddrs(reachable, unreachable, unknown []ma
unknown = append(unknown, a)
}
}

// primaryAddrs and secondaryAddrs are each sorted, but interleave in the
// buckets above (a secondary like webrtc-direct sorts before its quic-v1
// primary). Unsorted output makes removeNotInSource silently drop
// confirmed addrs, and makes removeInSource fail to filter unreachable
// addrs out of Addrs().
cmp := func(a, b ma.Multiaddr) int { return a.Compare(b) }
slices.SortFunc(reachable, cmp)
slices.SortFunc(unreachable, cmp)
slices.SortFunc(unknown, cmp)
return reachable, unreachable, unknown
}

Expand Down
35 changes: 35 additions & 0 deletions p2p/host/basic/addrs_reachability_tracker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,41 @@ func TestProbeManager(t *testing.T) {
})
}

func TestProbeManagerConfirmedAddrsSorted(t *testing.T) {
// Secondary transports can sort before their primary: webrtc-direct
// (protocol code 280) sorts before quic-v1 (461) on the same UDP socket.
// AppendConfirmedAddrs iterates primaries then secondaries, so without a
// final sort the buckets interleave; removeNotInSource in addrsManager
// then drops confirmed addrs, and removeInSource fails to filter
// unreachable addrs out of Addrs().
tcp := ma.StringCast("/ip4/1.2.3.4/tcp/4001")
wsSNI := ma.StringCast("/ip4/1.2.3.4/tcp/4001/tls/sni/*.example.net/ws")
quic := ma.StringCast("/ip4/1.2.3.4/udp/4001/quic-v1")
webrtc := ma.StringCast("/ip4/1.2.3.4/udp/4001/webrtc-direct")
wt := ma.StringCast("/ip4/1.2.3.4/udp/4001/quic-v1/webtransport")
addrs := []ma.Multiaddr{tcp, wsSNI, quic, webrtc, wt}

cl := clock.NewMock()
pm := newProbeManager(cl.Now)
pm.UpdateAddrs(slices.Clone(addrs))

for {
reqs := pm.GetProbe()
if len(reqs) == 0 {
break
}
pm.MarkProbeInProgress(reqs)
pm.CompleteProbe(reqs, autonatv2.Result{Addr: reqs[0].Addr, Idx: 0, Reachability: network.ReachabilityPublic}, nil)
}

reachable, unreachable, unknown := pm.AppendConfirmedAddrs(nil, nil, nil)
require.Empty(t, unreachable)
require.Empty(t, unknown)
matest.AssertMultiaddrsMatch(t, addrs, reachable)
require.True(t, slices.IsSortedFunc(reachable, func(a, b ma.Multiaddr) int { return a.Compare(b) }),
"reachable addrs not sorted: %v", reachable)
}

type mockAutoNATClient struct {
F func(context.Context, []autonatv2.Request) (autonatv2.Result, error)
}
Expand Down
Loading