|
|
@ -2,7 +2,6 @@ package autonat |
|
|
|
|
|
|
|
import ( |
|
|
|
"context" |
|
|
|
"errors" |
|
|
|
"math/rand" |
|
|
|
"sync/atomic" |
|
|
|
"time" |
|
|
@ -20,6 +19,8 @@ import ( |
|
|
|
|
|
|
|
var log = logging.Logger("autonat") |
|
|
|
|
|
|
|
const maxConfidence = 3 |
|
|
|
|
|
|
|
// AmbientAutoNAT is the implementation of ambient NAT autodiscovery
|
|
|
|
type AmbientAutoNAT struct { |
|
|
|
host host.Host |
|
|
@ -31,9 +32,9 @@ type AmbientAutoNAT struct { |
|
|
|
backgroundRunning chan struct{} // is closed when the background go routine exits
|
|
|
|
|
|
|
|
inboundConn chan network.Conn |
|
|
|
observations chan autoNATResult |
|
|
|
observations chan network.Reachability |
|
|
|
// status is an autoNATResult reflecting current status.
|
|
|
|
status atomic.Pointer[autoNATResult] |
|
|
|
status atomic.Pointer[network.Reachability] |
|
|
|
// Reflects the confidence on of the NATStatus being private, as a single
|
|
|
|
// dialback may fail for reasons unrelated to NAT.
|
|
|
|
// If it is <3, then multiple autoNAT peers may be contacted for dialback
|
|
|
@ -58,11 +59,6 @@ type StaticAutoNAT struct { |
|
|
|
service *autoNATService |
|
|
|
} |
|
|
|
|
|
|
|
type autoNATResult struct { |
|
|
|
network.Reachability |
|
|
|
address ma.Multiaddr |
|
|
|
} |
|
|
|
|
|
|
|
// New creates a new NAT autodiscovery system attached to a host
|
|
|
|
func New(h host.Host, options ...Option) (AutoNAT, error) { |
|
|
|
var err error |
|
|
@ -111,13 +107,14 @@ func New(h host.Host, options ...Option) (AutoNAT, error) { |
|
|
|
host: h, |
|
|
|
config: conf, |
|
|
|
inboundConn: make(chan network.Conn, 5), |
|
|
|
observations: make(chan autoNATResult, 1), |
|
|
|
observations: make(chan network.Reachability, 1), |
|
|
|
|
|
|
|
emitReachabilityChanged: emitReachabilityChanged, |
|
|
|
service: service, |
|
|
|
recentProbes: make(map[peer.ID]time.Time), |
|
|
|
} |
|
|
|
as.status.Store(&autoNATResult{network.ReachabilityUnknown, nil}) |
|
|
|
reachability := network.ReachabilityUnknown |
|
|
|
as.status.Store(&reachability) |
|
|
|
|
|
|
|
subscriber, err := as.host.EventBus().Subscribe( |
|
|
|
[]any{new(event.EvtLocalAddressesUpdated), new(event.EvtPeerIdentificationCompleted)}, |
|
|
@ -137,25 +134,15 @@ func New(h host.Host, options ...Option) (AutoNAT, error) { |
|
|
|
// Status returns the AutoNAT observed reachability status.
|
|
|
|
func (as *AmbientAutoNAT) Status() network.Reachability { |
|
|
|
s := as.status.Load() |
|
|
|
return s.Reachability |
|
|
|
return *s |
|
|
|
} |
|
|
|
|
|
|
|
func (as *AmbientAutoNAT) emitStatus() { |
|
|
|
status := as.status.Load() |
|
|
|
as.emitReachabilityChanged.Emit(event.EvtLocalReachabilityChanged{Reachability: status.Reachability}) |
|
|
|
status := *as.status.Load() |
|
|
|
as.emitReachabilityChanged.Emit(event.EvtLocalReachabilityChanged{Reachability: status}) |
|
|
|
if as.metricsTracer != nil { |
|
|
|
as.metricsTracer.ReachabilityStatus(status.Reachability) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// PublicAddr returns the publicly connectable Multiaddr of this node if one is known.
|
|
|
|
func (as *AmbientAutoNAT) PublicAddr() (ma.Multiaddr, error) { |
|
|
|
s := as.status.Load() |
|
|
|
if s.Reachability != network.ReachabilityPublic { |
|
|
|
return nil, errors.New("NAT status is not public") |
|
|
|
as.metricsTracer.ReachabilityStatus(status) |
|
|
|
} |
|
|
|
|
|
|
|
return s.address, nil |
|
|
|
} |
|
|
|
|
|
|
|
func ipInList(candidate ma.Multiaddr, list []ma.Multiaddr) bool { |
|
|
@ -174,7 +161,6 @@ func (as *AmbientAutoNAT) background() { |
|
|
|
// before starting autodetection
|
|
|
|
delay := as.config.bootDelay |
|
|
|
|
|
|
|
var lastAddrUpdated time.Time |
|
|
|
subChan := as.subscriber.Out() |
|
|
|
defer as.subscriber.Close() |
|
|
|
defer as.emitReachabilityChanged.Close() |
|
|
@ -187,10 +173,6 @@ func (as *AmbientAutoNAT) background() { |
|
|
|
// new inbound connection.
|
|
|
|
case conn := <-as.inboundConn: |
|
|
|
localAddrs := as.host.Addrs() |
|
|
|
ca := as.status.Load() |
|
|
|
if ca.address != nil { |
|
|
|
localAddrs = append(localAddrs, ca.address) |
|
|
|
} |
|
|
|
if manet.IsPublicAddr(conn.RemoteMultiaddr()) && |
|
|
|
!ipInList(conn.RemoteMultiaddr(), localAddrs) { |
|
|
|
as.lastInbound = time.Now() |
|
|
@ -199,16 +181,15 @@ func (as *AmbientAutoNAT) background() { |
|
|
|
case e := <-subChan: |
|
|
|
switch e := e.(type) { |
|
|
|
case event.EvtLocalAddressesUpdated: |
|
|
|
if !lastAddrUpdated.Add(time.Second).After(time.Now()) { |
|
|
|
lastAddrUpdated = time.Now() |
|
|
|
if as.confidence > 1 { |
|
|
|
as.confidence-- |
|
|
|
} |
|
|
|
// On local address update, reduce confidence from maximum so that we schedule
|
|
|
|
// the next probe sooner
|
|
|
|
if as.confidence == maxConfidence { |
|
|
|
as.confidence-- |
|
|
|
} |
|
|
|
case event.EvtPeerIdentificationCompleted: |
|
|
|
if s, err := as.host.Peerstore().SupportsProtocols(e.Peer, AutoNATProto); err == nil && len(s) > 0 { |
|
|
|
currentStatus := as.status.Load() |
|
|
|
if currentStatus.Reachability == network.ReachabilityUnknown { |
|
|
|
currentStatus := *as.status.Load() |
|
|
|
if currentStatus == network.ReachabilityUnknown { |
|
|
|
as.tryProbe(e.Peer) |
|
|
|
} |
|
|
|
} |
|
|
@ -256,7 +237,7 @@ func (as *AmbientAutoNAT) scheduleProbe() time.Duration { |
|
|
|
// * recent inbound connections (implying continued connectivity) should decrease the retry when public
|
|
|
|
// * recent inbound connections when not public mean we should try more actively to see if we're public.
|
|
|
|
fixedNow := time.Now() |
|
|
|
currentStatus := as.status.Load() |
|
|
|
currentStatus := *as.status.Load() |
|
|
|
|
|
|
|
nextProbe := fixedNow |
|
|
|
// Don't look for peers in the peer store more than once per second.
|
|
|
@ -268,13 +249,13 @@ func (as *AmbientAutoNAT) scheduleProbe() time.Duration { |
|
|
|
} |
|
|
|
if !as.lastProbe.IsZero() { |
|
|
|
untilNext := as.config.refreshInterval |
|
|
|
if currentStatus.Reachability == network.ReachabilityUnknown { |
|
|
|
if currentStatus == network.ReachabilityUnknown { |
|
|
|
untilNext = as.config.retryInterval |
|
|
|
} else if as.confidence < 3 { |
|
|
|
} else if as.confidence < maxConfidence { |
|
|
|
untilNext = as.config.retryInterval |
|
|
|
} else if currentStatus.Reachability == network.ReachabilityPublic && as.lastInbound.After(as.lastProbe) { |
|
|
|
} else if currentStatus == network.ReachabilityPublic && as.lastInbound.After(as.lastProbe) { |
|
|
|
untilNext *= 2 |
|
|
|
} else if currentStatus.Reachability != network.ReachabilityPublic && as.lastInbound.After(as.lastProbe) { |
|
|
|
} else if currentStatus != network.ReachabilityPublic && as.lastInbound.After(as.lastProbe) { |
|
|
|
untilNext /= 5 |
|
|
|
} |
|
|
|
|
|
|
@ -289,14 +270,14 @@ func (as *AmbientAutoNAT) scheduleProbe() time.Duration { |
|
|
|
} |
|
|
|
|
|
|
|
// Update the current status based on an observed result.
|
|
|
|
func (as *AmbientAutoNAT) recordObservation(observation autoNATResult) { |
|
|
|
currentStatus := as.status.Load() |
|
|
|
func (as *AmbientAutoNAT) recordObservation(observation network.Reachability) { |
|
|
|
currentStatus := *as.status.Load() |
|
|
|
|
|
|
|
if observation.Reachability == network.ReachabilityPublic { |
|
|
|
log.Debugf("NAT status is public") |
|
|
|
if observation == network.ReachabilityPublic { |
|
|
|
changed := false |
|
|
|
if currentStatus.Reachability != network.ReachabilityPublic { |
|
|
|
if currentStatus != network.ReachabilityPublic { |
|
|
|
// Aggressively switch to public from other states ignoring confidence
|
|
|
|
log.Debugf("NAT status is public") |
|
|
|
|
|
|
|
// we are flipping our NATStatus, so confidence drops to 0
|
|
|
|
as.confidence = 0 |
|
|
@ -304,19 +285,20 @@ func (as *AmbientAutoNAT) recordObservation(observation autoNATResult) { |
|
|
|
as.service.Enable() |
|
|
|
} |
|
|
|
changed = true |
|
|
|
} else if as.confidence < 3 { |
|
|
|
} else if as.confidence < maxConfidence { |
|
|
|
as.confidence++ |
|
|
|
} |
|
|
|
as.status.Store(&observation) |
|
|
|
if changed { |
|
|
|
as.emitStatus() |
|
|
|
} |
|
|
|
} else if observation.Reachability == network.ReachabilityPrivate { |
|
|
|
log.Debugf("NAT status is private") |
|
|
|
if currentStatus.Reachability != network.ReachabilityPrivate { |
|
|
|
} else if observation == network.ReachabilityPrivate { |
|
|
|
if currentStatus != network.ReachabilityPrivate { |
|
|
|
if as.confidence > 0 { |
|
|
|
as.confidence-- |
|
|
|
} else { |
|
|
|
log.Debugf("NAT status is private") |
|
|
|
|
|
|
|
// we are flipping our NATStatus, so confidence drops to 0
|
|
|
|
as.confidence = 0 |
|
|
|
as.status.Store(&observation) |
|
|
@ -325,7 +307,7 @@ func (as *AmbientAutoNAT) recordObservation(observation autoNATResult) { |
|
|
|
} |
|
|
|
as.emitStatus() |
|
|
|
} |
|
|
|
} else if as.confidence < 3 { |
|
|
|
} else if as.confidence < maxConfidence { |
|
|
|
as.confidence++ |
|
|
|
as.status.Store(&observation) |
|
|
|
} |
|
|
@ -334,8 +316,8 @@ func (as *AmbientAutoNAT) recordObservation(observation autoNATResult) { |
|
|
|
as.confidence-- |
|
|
|
} else { |
|
|
|
log.Debugf("NAT status is unknown") |
|
|
|
as.status.Store(&autoNATResult{network.ReachabilityUnknown, nil}) |
|
|
|
if currentStatus.Reachability != network.ReachabilityUnknown { |
|
|
|
as.status.Store(&observation) |
|
|
|
if currentStatus != network.ReachabilityUnknown { |
|
|
|
if as.service != nil { |
|
|
|
as.service.Enable() |
|
|
|
} |
|
|
@ -376,19 +358,18 @@ func (as *AmbientAutoNAT) probe(pi *peer.AddrInfo) { |
|
|
|
ctx, cancel := context.WithTimeout(as.ctx, as.config.requestTimeout) |
|
|
|
defer cancel() |
|
|
|
|
|
|
|
a, err := cli.DialBack(ctx, pi.ID) |
|
|
|
err := cli.DialBack(ctx, pi.ID) |
|
|
|
|
|
|
|
var result autoNATResult |
|
|
|
var result network.Reachability |
|
|
|
switch { |
|
|
|
case err == nil: |
|
|
|
log.Debugf("Dialback through %s successful; public address is %s", pi.ID.Pretty(), a.String()) |
|
|
|
result.Reachability = network.ReachabilityPublic |
|
|
|
result.address = a |
|
|
|
log.Debugf("Dialback through %s successful", pi.ID.Pretty()) |
|
|
|
result = network.ReachabilityPublic |
|
|
|
case IsDialError(err): |
|
|
|
log.Debugf("Dialback through %s failed", pi.ID.Pretty()) |
|
|
|
result.Reachability = network.ReachabilityPrivate |
|
|
|
result = network.ReachabilityPrivate |
|
|
|
default: |
|
|
|
result.Reachability = network.ReachabilityUnknown |
|
|
|
result = network.ReachabilityUnknown |
|
|
|
} |
|
|
|
|
|
|
|
select { |
|
|
@ -455,14 +436,6 @@ func (s *StaticAutoNAT) Status() network.Reachability { |
|
|
|
return s.reachability |
|
|
|
} |
|
|
|
|
|
|
|
// PublicAddr returns the publicly connectable Multiaddr of this node if one is known.
|
|
|
|
func (s *StaticAutoNAT) PublicAddr() (ma.Multiaddr, error) { |
|
|
|
if s.reachability != network.ReachabilityPublic { |
|
|
|
return nil, errors.New("NAT status is not public") |
|
|
|
} |
|
|
|
return nil, errors.New("no available address") |
|
|
|
} |
|
|
|
|
|
|
|
func (s *StaticAutoNAT) Close() error { |
|
|
|
if s.service != nil { |
|
|
|
s.service.Disable() |
|
|
|