Skip to content
This repository has been archived by the owner on May 11, 2022. It is now read-only.

Commit

Permalink
Merge pull request #53 from libp2p/feat/opt
Browse files Browse the repository at this point in the history
change autonat interface to use functional options
  • Loading branch information
willscott authored Mar 16, 2020
2 parents 4cd130c + 62f42d9 commit 898373b
Show file tree
Hide file tree
Showing 6 changed files with 120 additions and 55 deletions.
57 changes: 25 additions & 32 deletions autonat.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,28 +17,12 @@ import (
manet "github.com/multiformats/go-multiaddr-net"
)

var (
AutoNATBootDelay = 15 * time.Second
AutoNATRetryInterval = 90 * time.Second
AutoNATRefreshInterval = 15 * time.Minute
AutoNATRequestTimeout = 30 * time.Second
)

// AutoNAT is the interface for ambient NAT autodiscovery
type AutoNAT interface {
// Status returns the current NAT status
Status() network.Reachability
// PublicAddr returns the public dial address when NAT status is public and an
// error otherwise
PublicAddr() (ma.Multiaddr, error)
}

// AmbientAutoNAT is the implementation of ambient NAT autodiscovery
type AmbientAutoNAT struct {
ctx context.Context
host host.Host

getAddrs GetAddrs
*config

inboundConn chan network.Conn
observations chan autoNATResult
Expand All @@ -63,21 +47,30 @@ type autoNATResult struct {
address ma.Multiaddr
}

// NewAutoNAT creates a new ambient NAT autodiscovery instance attached to a host
// If getAddrs is nil, h.Addrs will be used
func NewAutoNAT(ctx context.Context, h host.Host, getAddrs GetAddrs) AutoNAT {
if getAddrs == nil {
getAddrs = h.Addrs
// New creates a new NAT autodiscovery system attached to a host
func New(ctx context.Context, h host.Host, options ...Option) (AutoNAT, error) {
conf := new(config)

if err := defaults(conf); err != nil {
return nil, err
}
if conf.getAddressFunc == nil {
conf.getAddressFunc = h.Addrs
}

subAddrUpdated, _ := h.EventBus().Subscribe(new(event.EvtLocalAddressesUpdated))
for _, o := range options {
if err := o(conf); err != nil {
return nil, err
}
}

subAddrUpdated, _ := h.EventBus().Subscribe(new(event.EvtLocalAddressesUpdated))
emitReachabilityChanged, _ := h.EventBus().Emitter(new(event.EvtLocalReachabilityChanged), eventbus.Stateful)

as := &AmbientAutoNAT{
ctx: ctx,
host: h,
getAddrs: getAddrs,
config: conf,
inboundConn: make(chan network.Conn, 5),
observations: make(chan autoNATResult, 1),

Expand All @@ -90,7 +83,7 @@ func NewAutoNAT(ctx context.Context, h host.Host, getAddrs GetAddrs) AutoNAT {
h.Network().Notify(as)
go as.background()

return as
return as, nil
}

// Status returns the AutoNAT observed reachability status.
Expand Down Expand Up @@ -127,7 +120,7 @@ func ipInList(candidate ma.Multiaddr, list []ma.Multiaddr) bool {
func (as *AmbientAutoNAT) background() {
// wait a bit for the node to come online and establish some connections
// before starting autodetection
delay := AutoNATBootDelay
delay := as.config.bootDelay

var lastAddrUpdated time.Time
addrUpdatedChan := as.subAddrUpdated.Out()
Expand Down Expand Up @@ -192,19 +185,19 @@ func (as *AmbientAutoNAT) scheduleProbe() time.Duration {

nextProbe := fixedNow
if !as.lastProbe.IsZero() {
untilNext := AutoNATRefreshInterval
untilNext := as.config.refreshInterval
if currentStatus.Reachability == network.ReachabilityUnknown {
untilNext = AutoNATRetryInterval
untilNext = as.config.retryInterval
} else if as.confidence < 3 {
untilNext = AutoNATRetryInterval
untilNext = as.config.retryInterval
} else if currentStatus.Reachability == network.ReachabilityPublic && as.lastInbound.After(as.lastProbe) {
untilNext *= 2
}
nextProbe = as.lastProbe.Add(untilNext)
}
if fixedNow.After(nextProbe) || fixedNow == nextProbe {
go as.probeNextPeer()
return AutoNATRetryInterval
return as.config.retryInterval
}
return nextProbe.Sub(fixedNow)
}
Expand Down Expand Up @@ -265,8 +258,8 @@ func (as *AmbientAutoNAT) recordObservation(observation autoNATResult) {
}

func (as *AmbientAutoNAT) probe(pi *peer.AddrInfo) {
cli := NewAutoNATClient(as.host, as.getAddrs)
ctx, cancel := context.WithTimeout(as.ctx, AutoNATRequestTimeout)
cli := NewAutoNATClient(as.host, as.config.getAddressFunc)
ctx, cancel := context.WithTimeout(as.ctx, as.config.requestTimeout)
defer cancel()

a, err := cli.DialBack(ctx, pi.ID)
Expand Down
9 changes: 1 addition & 8 deletions autonat_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,6 @@ import (
ma "github.com/multiformats/go-multiaddr"
)

func init() {
AutoNATBootDelay = 100 * time.Millisecond
AutoNATRefreshInterval = 1 * time.Second
AutoNATRetryInterval = 1 * time.Second
AutoNATIdentifyDelay = 100 * time.Millisecond
}

// these are mock service implementations for testing
func makeAutoNATServicePrivate(ctx context.Context, t *testing.T) host.Host {
h := bhost.NewBlankHost(swarmt.GenSwarm(t, ctx))
Expand Down Expand Up @@ -75,7 +68,7 @@ func makeAutoNAT(ctx context.Context, t *testing.T, ash host.Host) (host.Host, A
h := bhost.NewBlankHost(swarmt.GenSwarm(t, ctx))
h.Peerstore().AddAddrs(ash.ID(), ash.Addrs(), time.Minute)
h.Peerstore().AddProtocols(ash.ID(), AutoNATProto)
a := NewAutoNAT(ctx, h, nil)
a, _ := New(ctx, h, WithSchedule(100*time.Millisecond, time.Second), WithoutStartupDelay())
return h, a
}

Expand Down
12 changes: 1 addition & 11 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,15 @@ import (
ma "github.com/multiformats/go-multiaddr"
)

// AutoNATClient is a stateless client interface to AutoNAT peers
type AutoNATClient interface {
// DialBack requests from a peer providing AutoNAT services to test dial back
// and report the address on a successful connection.
DialBack(ctx context.Context, p peer.ID) (ma.Multiaddr, error)
}

// AutoNATError is the class of errors signalled by AutoNAT services
type AutoNATError struct {
Status pb.Message_ResponseStatus
Text string
}

// GetAddrs is a function that returns the addresses to dial back
type GetAddrs func() []ma.Multiaddr

// NewAutoNATClient creates a fresh instance of an AutoNATClient
// If getAddrs is nil, h.Addrs will be used
func NewAutoNATClient(h host.Host, getAddrs GetAddrs) AutoNATClient {
func NewAutoNATClient(h host.Host, getAddrs GetAddrs) Client {
if getAddrs == nil {
getAddrs = h.Addrs
}
Expand Down
32 changes: 32 additions & 0 deletions interface.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package autonat

import (
"context"

"github.com/libp2p/go-libp2p-core/network"
"github.com/libp2p/go-libp2p-core/peer"

ma "github.com/multiformats/go-multiaddr"
)

// AutoNAT is the interface for NAT autodiscovery
type AutoNAT interface {
// Status returns the current NAT status
Status() network.Reachability
// PublicAddr returns the public dial address when NAT status is public and an
// error otherwise
PublicAddr() (ma.Multiaddr, error)
}

// Client is a stateless client interface to AutoNAT peers
type Client interface {
// DialBack requests from a peer providing AutoNAT services to test dial back
// and report the address on a successful connection.
DialBack(ctx context.Context, p peer.ID) (ma.Multiaddr, error)
}

// GetAddrs is a function returning the candidate addresses for the local host.
type GetAddrs func() []ma.Multiaddr

// Option is an Autonat option for configuration
type Option func(*config) error
4 changes: 0 additions & 4 deletions notify.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package autonat

import (
"time"

"github.com/libp2p/go-libp2p-core/network"

ma "github.com/multiformats/go-multiaddr"
Expand All @@ -11,8 +9,6 @@ import (

var _ network.Notifiee = (*AmbientAutoNAT)(nil)

var AutoNATIdentifyDelay = 5 * time.Second

func (as *AmbientAutoNAT) Listen(net network.Network, a ma.Multiaddr) {}
func (as *AmbientAutoNAT) ListenClose(net network.Network, a ma.Multiaddr) {}
func (as *AmbientAutoNAT) OpenedStream(net network.Network, s network.Stream) {}
Expand Down
61 changes: 61 additions & 0 deletions options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package autonat

import (
"errors"
"time"
)

// config holds configurable options for the autonat subsystem.
type config struct {
getAddressFunc GetAddrs
bootDelay time.Duration
retryInterval time.Duration
refreshInterval time.Duration
requestTimeout time.Duration
}

var defaults = func(c *config) error {
c.bootDelay = 15 * time.Second
c.retryInterval = 90 * time.Second
c.refreshInterval = 15 * time.Minute
c.requestTimeout = 30 * time.Second

return nil
}

// WithAddresses allows overriding which Addresses the AutoNAT client beliieves
// are "its own". Useful for testing, or for more exotic port-forwarding
// scenarios where the host may be listening on different ports than it wants
// to externally advertise or verify connectability on.
func WithAddresses(addrFunc GetAddrs) Option {
return func(c *config) error {
if addrFunc == nil {
return errors.New("invalid address function supplied")
}
c.getAddressFunc = addrFunc
return nil
}
}

// WithSchedule configures how agressively probes will be made to verify the
// address of the host. retryInterval indicates how often probes should be made
// when the host lacks confident about its address, while refresh interval
// is the schedule of periodic probes when the host believes it knows its
// steady-state reachability.
func WithSchedule(retryInterval, refreshInterval time.Duration) Option {
return func(c *config) error {
c.retryInterval = retryInterval
c.refreshInterval = refreshInterval
return nil
}
}

// WithoutStartupDelay removes the initial delay the NAT subsystem typically
// uses as a buffer for ensuring that connectivity and guesses as to the hosts
// local interfaces have settled down during startup.
func WithoutStartupDelay() Option {
return func(c *config) error {
c.bootDelay = 1
return nil
}
}

0 comments on commit 898373b

Please sign in to comment.