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

change autonat interface to use functional options #53

Merged
merged 2 commits into from
Mar 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
}
willscott marked this conversation as resolved.
Show resolved Hide resolved

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// WithAddresses allows overriding which Addresses the AutoNAT client beliieves
// WithAddresses allows overriding which Addresses the AutoNAT client believes

// 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 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe call this AddressGuesser? As far as I can tell, that's really what this is.

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