diff --git a/autonat.go b/autonat.go index 410d733..60188c9 100644 --- a/autonat.go +++ b/autonat.go @@ -32,7 +32,7 @@ var ( AutoNATBootDelay = 15 * time.Second AutoNATRetryInterval = 90 * time.Second AutoNATRefreshInterval = 15 * time.Minute - AutoNATRequestTimeout = 60 * time.Second + AutoNATRequestTimeout = 30 * time.Second ) // AutoNAT is the interface for ambient NAT autodiscovery @@ -135,50 +135,84 @@ func (as *AmbientAutoNAT) autodetect() { } cli := NewAutoNATClient(as.host, as.getAddrs) - failures := 0 - - for _, pi := range peers { - ctx, cancel := context.WithTimeout(as.ctx, AutoNATRequestTimeout) - as.host.Peerstore().AddAddrs(pi.ID, pi.Addrs, pstore.TempAddrTTL) - a, err := cli.DialBack(ctx, pi.ID) - cancel() - - switch { - case err == nil: - log.Debugf("NAT status is public; address through %s: %s", pi.ID.Pretty(), a.String()) - as.mx.Lock() - as.addr = a - as.status = NATStatusPublic - as.confidence = 0 - as.mx.Unlock() - return + ctx, cancel := context.WithTimeout(as.ctx, AutoNATRequestTimeout) + defer cancel() + + var result struct { + sync.Mutex + private int + public int + pubaddr ma.Multiaddr + } - case IsDialError(err): - log.Debugf("dial error through %s: %s", pi.ID.Pretty(), err.Error()) - failures++ - if failures >= 3 || as.confidence >= 3 { // 3 times is enemy action - log.Debugf("NAT status is private") - as.mx.Lock() - as.status = NATStatusPrivate - as.confidence = 3 - as.mx.Unlock() - return - } + probe := 3 - as.confidence + if probe == 0 { + probe = 1 + } + if probe > len(peers) { + probe = len(peers) + } - default: - log.Debugf("Error dialing through %s: %s", pi.ID.Pretty(), err.Error()) - } + var wg sync.WaitGroup + + for _, pi := range peers[:probe] { + wg.Add(1) + go func(pi pstore.PeerInfo) { + defer wg.Done() + + as.host.Peerstore().AddAddrs(pi.ID, pi.Addrs, pstore.TempAddrTTL) + a, err := cli.DialBack(ctx, pi.ID) + + switch { + case err == nil: + log.Debugf("Dialback through %s successful; public address is %s", pi.ID.Pretty(), a.String()) + result.Lock() + result.public++ + result.pubaddr = a + result.Unlock() + + case IsDialError(err): + log.Debugf("Dialback through %s failed", pi.ID.Pretty()) + result.Lock() + result.private++ + result.Unlock() + + default: + log.Debugf("Dialback error through %s: %s", pi.ID.Pretty(), err) + } + }(pi) } + wg.Wait() + as.mx.Lock() - if failures > 0 { - as.status = NATStatusPrivate - as.confidence++ + if result.public > 0 { + log.Debugf("NAT status is public") + if as.status == NATStatusPrivate { + // we are flipping our NATStatus, so confidence drops to 0 + as.confidence = 0 + } else if as.confidence < 3 { + as.confidence++ + } + as.status = NATStatusPublic + as.addr = result.pubaddr + } else if result.private > 0 { log.Debugf("NAT status is private") + if as.status == NATStatusPublic { + // we are flipping our NATStatus, so confidence drops to 0 + as.confidence = 0 + } else if as.confidence < 3 { + as.confidence++ + } + as.status = NATStatusPrivate + as.addr = nil + } else if as.confidence > 0 { + // don't just flip to unknown, reduce confidence first + as.confidence-- } else { - as.status = NATStatusUnknown - as.confidence = 0 log.Debugf("NAT status is unknown") + as.status = NATStatusUnknown + as.addr = nil } as.mx.Unlock() }