diff --git a/core/event/reachability.go b/core/event/reachability.go index 8aa2cd07cc..18032544eb 100644 --- a/core/event/reachability.go +++ b/core/event/reachability.go @@ -14,10 +14,11 @@ type EvtLocalReachabilityChanged struct { } // EvtHostReachableAddrsChanged is sent when host's reachable or unreachable addresses change -// Reachable and Unreachable both contain only Public IP or DNS addresses +// Reachable, Unreachable, and Unknown only contain Public IP or DNS addresses // // Experimental: This API is unstable. Any changes to this event will be done without a deprecation notice. type EvtHostReachableAddrsChanged struct { Reachable []ma.Multiaddr Unreachable []ma.Multiaddr + Unknown []ma.Multiaddr } diff --git a/p2p/host/basic/addrs_manager.go b/p2p/host/basic/addrs_manager.go index 217570ae86..b1ee831c20 100644 --- a/p2p/host/basic/addrs_manager.go +++ b/p2p/host/basic/addrs_manager.go @@ -34,6 +34,7 @@ type hostAddrs struct { localAddrs []ma.Multiaddr reachableAddrs []ma.Multiaddr unreachableAddrs []ma.Multiaddr + unknownAddrs []ma.Multiaddr relayAddrs []ma.Multiaddr } @@ -250,9 +251,9 @@ func (a *addrsManager) updateAddrs(updateRelayAddrs bool, relayAddrs []ma.Multia defer a.addrsMx.Unlock() localAddrs := a.getLocalAddrs() - var currReachableAddrs, currUnreachableAddrs []ma.Multiaddr + var currReachableAddrs, currUnreachableAddrs, currUnknownAddrs []ma.Multiaddr if a.addrsReachabilityTracker != nil { - currReachableAddrs, currUnreachableAddrs = a.getConfirmedAddrs(localAddrs) + currReachableAddrs, currUnreachableAddrs, currUnknownAddrs = a.getConfirmedAddrs(localAddrs) } if !updateRelayAddrs { relayAddrs = a.currentAddrs.relayAddrs @@ -267,6 +268,7 @@ func (a *addrsManager) updateAddrs(updateRelayAddrs bool, relayAddrs []ma.Multia localAddrs: append(a.currentAddrs.localAddrs[:0], localAddrs...), reachableAddrs: append(a.currentAddrs.reachableAddrs[:0], currReachableAddrs...), unreachableAddrs: append(a.currentAddrs.unreachableAddrs[:0], currUnreachableAddrs...), + unknownAddrs: append(a.currentAddrs.unknownAddrs[:0], currUnknownAddrs...), relayAddrs: append(a.currentAddrs.relayAddrs[:0], relayAddrs...), } @@ -275,6 +277,7 @@ func (a *addrsManager) updateAddrs(updateRelayAddrs bool, relayAddrs []ma.Multia addrs: currAddrs, reachableAddrs: currReachableAddrs, unreachableAddrs: currUnreachableAddrs, + unknownAddrs: currUnknownAddrs, relayAddrs: relayAddrs, } } @@ -303,11 +306,13 @@ func (a *addrsManager) notifyAddrsChanged(emitter event.Emitter, previous, curre // We must send these events in the same order. It'll be confusing for consumers // if the reachable event is received after the addr removed event. if areAddrsDifferent(previous.reachableAddrs, current.reachableAddrs) || - areAddrsDifferent(previous.unreachableAddrs, current.unreachableAddrs) { + areAddrsDifferent(previous.unreachableAddrs, current.unreachableAddrs) || + areAddrsDifferent(previous.unknownAddrs, current.unknownAddrs) { log.Debugf("host reachable addrs updated: %s", current.localAddrs) if err := emitter.Emit(event.EvtHostReachableAddrsChanged{ Reachable: slices.Clone(current.reachableAddrs), Unreachable: slices.Clone(current.unreachableAddrs), + Unknown: slices.Clone(current.unknownAddrs), }); err != nil { log.Errorf("error sending host reachable addrs changed event: %s", err) } @@ -365,16 +370,16 @@ func (a *addrsManager) DirectAddrs() []ma.Multiaddr { return slices.Clone(a.currentAddrs.localAddrs) } -// ReachableAddrs returns all addresses of the host that are reachable from the internet -func (a *addrsManager) ReachableAddrs() []ma.Multiaddr { +// ConfirmedAddrs returns all addresses of the host that are reachable from the internet +func (a *addrsManager) ConfirmedAddrs() (reachable []ma.Multiaddr, unreachable []ma.Multiaddr, unknown []ma.Multiaddr) { a.addrsMx.RLock() defer a.addrsMx.RUnlock() - return slices.Clone(a.currentAddrs.reachableAddrs) + return slices.Clone(a.currentAddrs.reachableAddrs), slices.Clone(a.currentAddrs.unreachableAddrs), slices.Clone(a.currentAddrs.unknownAddrs) } -func (a *addrsManager) getConfirmedAddrs(localAddrs []ma.Multiaddr) (reachableAddrs, unreachableAddrs []ma.Multiaddr) { - reachableAddrs, unreachableAddrs = a.addrsReachabilityTracker.ConfirmedAddrs() - return removeNotInSource(reachableAddrs, localAddrs), removeNotInSource(unreachableAddrs, localAddrs) +func (a *addrsManager) getConfirmedAddrs(localAddrs []ma.Multiaddr) (reachableAddrs, unreachableAddrs, unknownAddrs []ma.Multiaddr) { + reachableAddrs, unreachableAddrs, unknownAddrs = a.addrsReachabilityTracker.ConfirmedAddrs() + return removeNotInSource(reachableAddrs, localAddrs), removeNotInSource(unreachableAddrs, localAddrs), removeNotInSource(unknownAddrs, localAddrs) } var p2pCircuitAddr = ma.StringCast("/p2p-circuit") diff --git a/p2p/host/basic/addrs_manager_test.go b/p2p/host/basic/addrs_manager_test.go index 56f9faaf42..f2532d9920 100644 --- a/p2p/host/basic/addrs_manager_test.go +++ b/p2p/host/basic/addrs_manager_test.go @@ -462,6 +462,20 @@ func TestAddrsManagerReachabilityEvent(t *testing.T) { }, }) + initialUnknownAddrs := []ma.Multiaddr{publicQUIC, publicTCP, publicQUIC2} + + // First event: all addresses are initially unknown + select { + case e := <-sub.Out(): + evt := e.(event.EvtHostReachableAddrsChanged) + require.Empty(t, evt.Reachable) + require.Empty(t, evt.Unreachable) + require.ElementsMatch(t, initialUnknownAddrs, evt.Unknown) + case <-time.After(5 * time.Second): + t.Fatal("expected initial event for reachability change") + } + + // Wait for probes to complete and addresses to be classified reachableAddrs := []ma.Multiaddr{publicQUIC} unreachableAddrs := []ma.Multiaddr{publicTCP, publicQUIC2} select { @@ -469,9 +483,13 @@ func TestAddrsManagerReachabilityEvent(t *testing.T) { evt := e.(event.EvtHostReachableAddrsChanged) require.ElementsMatch(t, reachableAddrs, evt.Reachable) require.ElementsMatch(t, unreachableAddrs, evt.Unreachable) - require.ElementsMatch(t, reachableAddrs, am.ReachableAddrs()) + require.Empty(t, evt.Unknown) + reachable, unreachable, unknown := am.ConfirmedAddrs() + require.ElementsMatch(t, reachable, reachableAddrs) + require.ElementsMatch(t, unreachable, unreachableAddrs) + require.Empty(t, unknown) case <-time.After(5 * time.Second): - t.Fatal("expected event for reachability change") + t.Fatal("expected final event for reachability change after probing") } } diff --git a/p2p/host/basic/addrs_reachability_tracker.go b/p2p/host/basic/addrs_reachability_tracker.go index 2d09a34ebc..923b04cf70 100644 --- a/p2p/host/basic/addrs_reachability_tracker.go +++ b/p2p/host/basic/addrs_reachability_tracker.go @@ -54,6 +54,7 @@ type addrsReachabilityTracker struct { mx sync.Mutex reachableAddrs []ma.Multiaddr unreachableAddrs []ma.Multiaddr + unknownAddrs []ma.Multiaddr } // newAddrsReachabilityTracker returns a new addrsReachabilityTracker. @@ -83,10 +84,10 @@ func (r *addrsReachabilityTracker) UpdateAddrs(addrs []ma.Multiaddr) { } } -func (r *addrsReachabilityTracker) ConfirmedAddrs() (reachableAddrs, unreachableAddrs []ma.Multiaddr) { +func (r *addrsReachabilityTracker) ConfirmedAddrs() (reachableAddrs, unreachableAddrs, unknownAddrs []ma.Multiaddr) { r.mx.Lock() defer r.mx.Unlock() - return slices.Clone(r.reachableAddrs), slices.Clone(r.unreachableAddrs) + return slices.Clone(r.reachableAddrs), slices.Clone(r.unreachableAddrs), slices.Clone(r.unknownAddrs) } func (r *addrsReachabilityTracker) Start() error { @@ -129,7 +130,7 @@ func (r *addrsReachabilityTracker) background() { var task reachabilityTask var backoffInterval time.Duration - var currReachable, currUnreachable, prevReachable, prevUnreachable []ma.Multiaddr + var currReachable, currUnreachable, currUnknown, prevReachable, prevUnreachable, prevUnknown []ma.Multiaddr for { select { case <-probeTicker.C: @@ -173,12 +174,13 @@ func (r *addrsReachabilityTracker) background() { return } - currReachable, currUnreachable = r.appendConfirmedAddrs(currReachable[:0], currUnreachable[:0]) - if areAddrsDifferent(prevReachable, currReachable) || areAddrsDifferent(prevUnreachable, currUnreachable) { + currReachable, currUnreachable, currUnknown = r.appendConfirmedAddrs(currReachable[:0], currUnreachable[:0], currUnknown[:0]) + if areAddrsDifferent(prevReachable, currReachable) || areAddrsDifferent(prevUnreachable, currUnreachable) || areAddrsDifferent(prevUnknown, currUnknown) { r.notify() } prevReachable = append(prevReachable[:0], currReachable...) prevUnreachable = append(prevUnreachable[:0], currUnreachable...) + prevUnknown = append(prevUnknown[:0], currUnknown...) if !nextProbeTime.IsZero() { probeTimer.Reset(nextProbeTime.Sub(r.clock.Now())) } @@ -196,13 +198,14 @@ func newBackoffInterval(current time.Duration) time.Duration { return current } -func (r *addrsReachabilityTracker) appendConfirmedAddrs(reachable, unreachable []ma.Multiaddr) (reachableAddrs, unreachableAddrs []ma.Multiaddr) { - reachable, unreachable = r.probeManager.AppendConfirmedAddrs(reachable, unreachable) +func (r *addrsReachabilityTracker) appendConfirmedAddrs(reachable, unreachable, unknown []ma.Multiaddr) (reachableAddrs, unreachableAddrs, unknownAddrs []ma.Multiaddr) { + reachable, unreachable, unknown = r.probeManager.AppendConfirmedAddrs(reachable, unreachable, unknown) r.mx.Lock() r.reachableAddrs = append(r.reachableAddrs[:0], reachable...) r.unreachableAddrs = append(r.unreachableAddrs[:0], unreachable...) + r.unknownAddrs = append(r.unknownAddrs[:0], unknown...) r.mx.Unlock() - return reachable, unreachable + return reachable, unreachable, unknown } func (r *addrsReachabilityTracker) notify() { @@ -381,7 +384,7 @@ func newProbeManager(now func() time.Time) *probeManager { } // AppendConfirmedAddrs appends the current confirmed reachable and unreachable addresses. -func (m *probeManager) AppendConfirmedAddrs(reachable, unreachable []ma.Multiaddr) (reachableAddrs, unreachableAddrs []ma.Multiaddr) { +func (m *probeManager) AppendConfirmedAddrs(reachable, unreachable, unknown []ma.Multiaddr) (reachableAddrs, unreachableAddrs, unknownAddrs []ma.Multiaddr) { m.mx.Lock() defer m.mx.Unlock() @@ -393,9 +396,11 @@ func (m *probeManager) AppendConfirmedAddrs(reachable, unreachable []ma.Multiadd reachable = append(reachable, a) case network.ReachabilityPrivate: unreachable = append(unreachable, a) + case network.ReachabilityUnknown: + unknown = append(unknown, a) } } - return reachable, unreachable + return reachable, unreachable, unknown } // UpdateAddrs updates the tracked addrs diff --git a/p2p/host/basic/addrs_reachability_tracker_test.go b/p2p/host/basic/addrs_reachability_tracker_test.go index a58b60db48..b2916a0de7 100644 --- a/p2p/host/basic/addrs_reachability_tracker_test.go +++ b/p2p/host/basic/addrs_reachability_tracker_test.go @@ -54,11 +54,11 @@ func TestProbeManager(t *testing.T) { } pm.CompleteProbe(reqs, autonatv2.Result{Addr: reqs[0].Addr, Idx: 0, Reachability: network.ReachabilityPublic}, nil) } - reachable, _ := pm.AppendConfirmedAddrs(nil, nil) + reachable, _, _ := pm.AppendConfirmedAddrs(nil, nil, nil) require.Equal(t, reachable, []ma.Multiaddr{pub1, pub2}) pm.UpdateAddrs([]ma.Multiaddr{pub3}) - reachable, _ = pm.AppendConfirmedAddrs(nil, nil) + reachable, _, _ = pm.AppendConfirmedAddrs(nil, nil, nil) require.Empty(t, reachable) require.Len(t, pm.statuses, 1) }) @@ -173,7 +173,7 @@ func TestProbeManager(t *testing.T) { } } - reachable, unreachable := pm.AppendConfirmedAddrs(nil, nil) + reachable, unreachable, _ := pm.AppendConfirmedAddrs(nil, nil, nil) require.Equal(t, reachable, []ma.Multiaddr{pub1}) require.Equal(t, unreachable, []ma.Multiaddr{pub2}) }) @@ -184,12 +184,12 @@ func TestProbeManager(t *testing.T) { pm.CompleteProbe(reqs, autonatv2.Result{Addr: pub1, Idx: 0, Reachability: network.ReachabilityPublic}, nil) } - reachable, unreachable := pm.AppendConfirmedAddrs(nil, nil) + reachable, unreachable, _ := pm.AppendConfirmedAddrs(nil, nil, nil) require.Equal(t, reachable, []ma.Multiaddr{pub1}) require.Empty(t, unreachable) cl.Add(maxProbeResultTTL + 1*time.Second) - reachable, unreachable = pm.AppendConfirmedAddrs(nil, nil) + reachable, unreachable, _ = pm.AppendConfirmedAddrs(nil, nil, nil) require.Empty(t, reachable) require.Empty(t, unreachable) }) @@ -211,6 +211,18 @@ func TestAddrsReachabilityTracker(t *testing.T) { pub3 := ma.StringCast("/ip4/1.1.1.3/tcp/1") pri := ma.StringCast("/ip4/192.168.1.1/tcp/1") + assertFirstEvent := func(t *testing.T, tr *addrsReachabilityTracker, addrs []ma.Multiaddr) { + select { + case <-tr.reachabilityUpdateCh: + case <-time.After(200 * time.Millisecond): + t.Fatal("expected first event quickly") + } + reachable, unreachable, unknown := tr.ConfirmedAddrs() + require.Empty(t, reachable) + require.Empty(t, unreachable) + require.ElementsMatch(t, unknown, addrs, "%s %s", unknown, addrs) + } + newTracker := func(cli mockAutoNATClient, cl clock.Clock) *addrsReachabilityTracker { ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) if cl == nil { @@ -247,29 +259,34 @@ func TestAddrsReachabilityTracker(t *testing.T) { return autonatv2.Result{Addr: pub2, Idx: i, Reachability: network.ReachabilityPrivate}, nil } } - return autonatv2.Result{}, autonatv2.ErrNoPeers + return autonatv2.Result{AllAddrsRefused: true}, nil }, } tr := newTracker(mockClient, nil) tr.UpdateAddrs([]ma.Multiaddr{pub2, pub1, pri}) + assertFirstEvent(t, tr, []ma.Multiaddr{pub1, pub2}) + select { case <-tr.reachabilityUpdateCh: case <-time.After(2 * time.Second): t.Fatal("expected reachability update") } - reachable, unreachable := tr.ConfirmedAddrs() + reachable, unreachable, unknown := tr.ConfirmedAddrs() require.Equal(t, reachable, []ma.Multiaddr{pub1}, "%s %s", reachable, pub1) require.Equal(t, unreachable, []ma.Multiaddr{pub2}, "%s %s", unreachable, pub2) + require.Empty(t, unknown) - tr.UpdateAddrs([]ma.Multiaddr{pub3, pub1, pri}) + tr.UpdateAddrs([]ma.Multiaddr{pub3, pub1, pub2, pri}) select { case <-tr.reachabilityUpdateCh: case <-time.After(2 * time.Second): t.Fatal("expected reachability update") } - reachable, unreachable = tr.ConfirmedAddrs() + reachable, unreachable, unknown = tr.ConfirmedAddrs() + t.Logf("Second probe - Reachable: %v, Unreachable: %v, Unknown: %v", reachable, unreachable, unknown) require.Equal(t, reachable, []ma.Multiaddr{pub1}, "%s %s", reachable, pub1) - require.Empty(t, unreachable) + require.Equal(t, unreachable, []ma.Multiaddr{pub2}, "%s %s", unreachable, pub2) + require.Equal(t, unknown, []ma.Multiaddr{pub3}, "%s %s", unknown, pub3) }) t.Run("confirmed addrs ordering", func(t *testing.T) { @@ -285,12 +302,14 @@ func TestAddrsReachabilityTracker(t *testing.T) { } slices.SortFunc(addrs, func(a, b ma.Multiaddr) int { return -a.Compare(b) }) // sort in reverse order tr.UpdateAddrs(addrs) + assertFirstEvent(t, tr, addrs) + select { case <-tr.reachabilityUpdateCh: case <-time.After(2 * time.Second): t.Fatal("expected reachability update") } - reachable, unreachable := tr.ConfirmedAddrs() + reachable, unreachable, _ := tr.ConfirmedAddrs() require.Empty(t, unreachable) orderedAddrs := slices.Clone(addrs) @@ -334,6 +353,7 @@ func TestAddrsReachabilityTracker(t *testing.T) { // update addrs and wait for initial checks tr.UpdateAddrs([]ma.Multiaddr{pub1}) + assertFirstEvent(t, tr, []ma.Multiaddr{pub1}) // need to update clock after the background goroutine processes the new addrs time.Sleep(100 * time.Millisecond) cl.Add(1) @@ -356,7 +376,7 @@ func TestAddrsReachabilityTracker(t *testing.T) { case <-time.After(1 * time.Second): t.Fatal("expected probe") } - reachable, unreachable := tr.ConfirmedAddrs() + reachable, unreachable, _ := tr.ConfirmedAddrs() require.Empty(t, reachable) require.Empty(t, unreachable) } @@ -368,7 +388,7 @@ func TestAddrsReachabilityTracker(t *testing.T) { case <-time.After(1 * time.Second): t.Fatal("unexpected reachability update") } - reachable, unreachable := tr.ConfirmedAddrs() + reachable, unreachable, _ := tr.ConfirmedAddrs() require.Equal(t, reachable, []ma.Multiaddr{pub1}) require.Empty(t, unreachable) }) @@ -391,6 +411,8 @@ func TestAddrsReachabilityTracker(t *testing.T) { tr := newTracker(mockClient, nil) tr.UpdateAddrs([]ma.Multiaddr{pub1}) + assertFirstEvent(t, tr, []ma.Multiaddr{pub1}) + for i := 0; i < minConfidence; i++ { select { case <-notify: @@ -400,7 +422,7 @@ func TestAddrsReachabilityTracker(t *testing.T) { } select { case <-tr.reachabilityUpdateCh: - reachable, unreachable := tr.ConfirmedAddrs() + reachable, unreachable, _ := tr.ConfirmedAddrs() require.Equal(t, reachable, []ma.Multiaddr{pub1}) require.Empty(t, unreachable) case <-time.After(1 * time.Second): @@ -415,7 +437,7 @@ func TestAddrsReachabilityTracker(t *testing.T) { tr.UpdateAddrs([]ma.Multiaddr{pub2}) select { case <-tr.reachabilityUpdateCh: - reachable, unreachable := tr.ConfirmedAddrs() + reachable, unreachable, _ := tr.ConfirmedAddrs() require.Empty(t, reachable) require.Empty(t, unreachable) case <-time.After(1 * time.Second): @@ -455,6 +477,7 @@ func TestAddrsReachabilityTracker(t *testing.T) { // update addrs and wait for initial checks tr.UpdateAddrs([]ma.Multiaddr{pub1}) + assertFirstEvent(t, tr, []ma.Multiaddr{pub1}) // need to update clock after the background goroutine processes the new addrs time.Sleep(100 * time.Millisecond) cl.Add(1) @@ -591,7 +614,7 @@ func TestRefreshReachability(t *testing.T) { result := r.refreshReachability() require.False(t, <-result.BackoffCh) - reachable, unreachable := pm.AppendConfirmedAddrs(nil, nil) + reachable, unreachable, _ := pm.AppendConfirmedAddrs(nil, nil, nil) require.Equal(t, reachable, []ma.Multiaddr{pub1}) require.Empty(t, unreachable) require.Equal(t, pm.InProgressProbes(), 0) @@ -617,7 +640,7 @@ func TestRefreshReachability(t *testing.T) { result := r.refreshReachability() require.False(t, <-result.BackoffCh) - reachable, unreachable := pm.AppendConfirmedAddrs(nil, nil) + reachable, unreachable, _ := pm.AppendConfirmedAddrs(nil, nil, nil) require.Equal(t, reachable, []ma.Multiaddr{pub1}) require.Equal(t, unreachable, []ma.Multiaddr{pub2}) require.Equal(t, pm.InProgressProbes(), 0) diff --git a/p2p/host/basic/basic_host.go b/p2p/host/basic/basic_host.go index d7b9f40ab2..70c6b2a1a2 100644 --- a/p2p/host/basic/basic_host.go +++ b/p2p/host/basic/basic_host.go @@ -752,14 +752,14 @@ func (h *BasicHost) AllAddrs() []ma.Multiaddr { return h.addressManager.DirectAddrs() } -// ReachableAddrs returns all addresses of the host that are reachable from the internet +// ConfirmedAddrs returns all addresses of the host grouped by their reachability // as verified by autonatv2. // // Experimental: This API may change in the future without deprecation. // // Requires AutoNATv2 to be enabled. -func (h *BasicHost) ReachableAddrs() []ma.Multiaddr { - return h.addressManager.ReachableAddrs() +func (h *BasicHost) ConfirmedAddrs() (reachable []ma.Multiaddr, unreachable []ma.Multiaddr, unknown []ma.Multiaddr) { + return h.addressManager.ConfirmedAddrs() } func trimHostAddrList(addrs []ma.Multiaddr, maxSize int) []ma.Multiaddr {