diff --git a/p2p/host/basic/addrs_manager.go b/p2p/host/basic/addrs_manager.go index c160291b4a..cc90de4012 100644 --- a/p2p/host/basic/addrs_manager.go +++ b/p2p/host/basic/addrs_manager.go @@ -479,7 +479,7 @@ func (a *addrsManager) applyAddrsFactory(addrs []ma.Multiaddr) []ma.Multiaddr { addrs = append(addrs[:0], af...) // Add certhashes for the addresses provided by the user via address factory. addrs = a.addCertHashes(ma.Unique(addrs)) - slices.SortFunc(addrs, func(a, b ma.Multiaddr) int { return a.Compare(b) }) + slices.SortFunc(addrs, ma.Multiaddr.Compare) return addrs } @@ -514,6 +514,12 @@ func (a *addrsManager) ConfirmedAddrs() (reachable []ma.Multiaddr, unreachable [ func (a *addrsManager) getConfirmedAddrs(localAddrs []ma.Multiaddr) (reachableAddrs, unreachableAddrs, unknownAddrs []ma.Multiaddr) { reachableAddrs, unreachableAddrs, unknownAddrs = a.addrsReachabilityTracker.ConfirmedAddrs() + // Don't rely on tracker's ordering. removeNotInSource here and removeInSource in + // getDialableAddrs require sorted input; unsorted input silently drops + // confirmed addrs. + slices.SortFunc(reachableAddrs, ma.Multiaddr.Compare) + slices.SortFunc(unreachableAddrs, ma.Multiaddr.Compare) + slices.SortFunc(unknownAddrs, ma.Multiaddr.Compare) return removeNotInSource(reachableAddrs, localAddrs), removeNotInSource(unreachableAddrs, localAddrs), removeNotInSource(unknownAddrs, localAddrs) } @@ -551,7 +557,7 @@ func (a *addrsManager) getLocalAddrs() []ma.Multiaddr { // using identify. finalAddrs = a.addCertHashes(finalAddrs) finalAddrs = ma.Unique(finalAddrs) - slices.SortFunc(finalAddrs, func(a, b ma.Multiaddr) int { return a.Compare(b) }) + slices.SortFunc(finalAddrs, ma.Multiaddr.Compare) return finalAddrs } @@ -691,8 +697,8 @@ func areAddrsDifferent(prev, current []ma.Multiaddr) bool { if len(prev) != len(current) { return true } - slices.SortFunc(prev, func(a, b ma.Multiaddr) int { return a.Compare(b) }) - slices.SortFunc(current, func(a, b ma.Multiaddr) int { return a.Compare(b) }) + slices.SortFunc(prev, ma.Multiaddr.Compare) + slices.SortFunc(current, ma.Multiaddr.Compare) for i := range prev { if !prev[i].Equal(current[i]) { return true diff --git a/p2p/host/basic/addrs_manager_test.go b/p2p/host/basic/addrs_manager_test.go index ee74a8bbbe..967af290d0 100644 --- a/p2p/host/basic/addrs_manager_test.go +++ b/p2p/host/basic/addrs_manager_test.go @@ -449,6 +449,53 @@ func TestAddrsManagerReachabilityEvent(t *testing.T) { } } +func TestAddrsManagerConfirmedAddrsIncludesSecondaryTransports(t *testing.T) { + // A node listening on every transport kubo enables by default, on two UDP + // sockets: one reachable, one not. Each UDP bucket interleaves under + // Multiaddr.Compare (webrtc-direct sorts before its quic-v1 primary), and + // getConfirmedAddrs and Addrs merge the buckets with helpers that + // silently drop entries on unsorted input: removeNotInSource must keep + // all confirmed addrs in both the reachable and unreachable buckets, and + // removeInSource must remove every confirmed-unreachable addr from 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") + quicPub := ma.StringCast("/ip4/1.2.3.4/udp/4002/quic-v1") + webrtcPub := ma.StringCast("/ip4/1.2.3.4/udp/4002/webrtc-direct") + wtPub := ma.StringCast("/ip4/1.2.3.4/udp/4002/quic-v1/webtransport") + quicPriv := ma.StringCast("/ip4/1.2.3.4/udp/4001/quic-v1") + webrtcPriv := ma.StringCast("/ip4/1.2.3.4/udp/4001/webrtc-direct") + wtPriv := ma.StringCast("/ip4/1.2.3.4/udp/4001/quic-v1/webtransport") + reachableAddrs := []ma.Multiaddr{tcp, wsSNI, quicPub, webrtcPub, wtPub} + unreachableAddrs := []ma.Multiaddr{quicPriv, webrtcPriv, wtPriv} + listenAddrs := append(slices.Clone(reachableAddrs), unreachableAddrs...) + + // The UDP socket on port 4001 is unreachable, everything else is reachable. + am := newAddrsManagerTestCase(t, addrsManagerArgs{ + ListenAddrs: func() []ma.Multiaddr { return listenAddrs }, + AutoNATClient: mockAutoNATClient{ + F: func(_ context.Context, reqs []autonatv2.Request) (autonatv2.Result, error) { + rch := network.ReachabilityPublic + if port, err := reqs[0].Addr.ValueForProtocol(ma.P_UDP); err == nil && port == "4001" { + rch = network.ReachabilityPrivate + } + return autonatv2.Result{Addr: reqs[0].Addr, Idx: 0, Reachability: rch}, nil + }, + }, + }) + defer am.Close() + + require.Eventually(t, func() bool { + reachable, unreachable, _ := am.ConfirmedAddrs() + return len(reachable) == len(reachableAddrs) && len(unreachable) == len(unreachableAddrs) + }, 5*time.Second, 50*time.Millisecond, "expected all listen addrs to be confirmed") + + reachable, unreachable, unknown := am.ConfirmedAddrs() + matest.AssertMultiaddrsMatch(t, reachableAddrs, reachable) + matest.AssertMultiaddrsMatch(t, unreachableAddrs, unreachable) + require.Empty(t, unknown) + matest.AssertMultiaddrsMatch(t, reachableAddrs, am.Addrs()) +} + 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") diff --git a/p2p/host/basic/addrs_reachability_tracker.go b/p2p/host/basic/addrs_reachability_tracker.go index 0f802977df..5e68847df4 100644 --- a/p2p/host/basic/addrs_reachability_tracker.go +++ b/p2p/host/basic/addrs_reachability_tracker.go @@ -433,7 +433,7 @@ func (m *probeManager) UpdateAddrs(addrs []ma.Multiaddr) { m.mx.Lock() defer m.mx.Unlock() - slices.SortFunc(addrs, func(a, b ma.Multiaddr) int { return a.Compare(b) }) + slices.SortFunc(addrs, ma.Multiaddr.Compare) statuses := make(map[string]*addrStatus, len(addrs)) for _, addr := range addrs { k := string(addr.Bytes())