autonatv2: add Unknown addrs to event (#3305)

Providing unknown addresses to the user, helps them infer whether autonatv2 has any more addresses it might confirm.

There's no upside to not sending this information.

This also changes host.ConfirmedAddrs to return all the three categories, Reachable, Unreachable, and Unknown addrs.
This commit is contained in:
sukun
2025-06-10 21:02:18 +05:30
committed by GitHub
parent b82a39cb89
commit dbf7e1b972
6 changed files with 94 additions and 42 deletions

View File

@@ -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
}

View File

@@ -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")

View File

@@ -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")
}
}

View File

@@ -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

View File

@@ -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)

View File

@@ -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 {