Skip to content

Commit

Permalink
autonatv2: implement autonatv2 spec (#2469)
Browse files Browse the repository at this point in the history
  • Loading branch information
sukunrt authored Jun 21, 2024
1 parent 6cebdd8 commit 87c4355
Show file tree
Hide file tree
Showing 24 changed files with 3,555 additions and 173 deletions.
99 changes: 95 additions & 4 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"github.com/libp2p/go-libp2p/p2p/host/autonat"
"github.com/libp2p/go-libp2p/p2p/host/autorelay"
bhost "github.com/libp2p/go-libp2p/p2p/host/basic"
blankhost "github.com/libp2p/go-libp2p/p2p/host/blank"
"github.com/libp2p/go-libp2p/p2p/host/eventbus"
"github.com/libp2p/go-libp2p/p2p/host/peerstore/pstoremem"
rcmgr "github.com/libp2p/go-libp2p/p2p/host/resource-manager"
Expand Down Expand Up @@ -131,6 +132,13 @@ type Config struct {
SwarmOpts []swarm.Option

DisableIdentifyAddressDiscovery bool

EnableAutoNATv2 bool

UDPBlackHoleSuccessCounter *swarm.BlackHoleSuccessCounter
CustomUDPBlackHoleSuccessCounter bool
IPv6BlackHoleSuccessCounter *swarm.BlackHoleSuccessCounter
CustomIPv6BlackHoleSuccessCounter bool
}

func (cfg *Config) makeSwarm(eventBus event.Bus, enableMetrics bool) (*swarm.Swarm, error) {
Expand Down Expand Up @@ -165,7 +173,10 @@ func (cfg *Config) makeSwarm(eventBus event.Bus, enableMetrics bool) (*swarm.Swa
return nil, err
}

opts := cfg.SwarmOpts
opts := append(cfg.SwarmOpts,
swarm.WithUDPBlackHoleSuccessCounter(cfg.UDPBlackHoleSuccessCounter),
swarm.WithIPv6BlackHoleSuccessCounter(cfg.IPv6BlackHoleSuccessCounter),
)
if cfg.Reporter != nil {
opts = append(opts, swarm.WithMetrics(cfg.Reporter))
}
Expand Down Expand Up @@ -193,6 +204,77 @@ func (cfg *Config) makeSwarm(eventBus event.Bus, enableMetrics bool) (*swarm.Swa
return swarm.NewSwarm(pid, cfg.Peerstore, eventBus, opts...)
}

func (cfg *Config) makeAutoNATV2Host() (host.Host, error) {
autonatPrivKey, _, err := crypto.GenerateEd25519Key(rand.Reader)
if err != nil {
return nil, err
}
ps, err := pstoremem.NewPeerstore()
if err != nil {
return nil, err
}

autoNatCfg := Config{
Transports: cfg.Transports,
Muxers: cfg.Muxers,
SecurityTransports: cfg.SecurityTransports,
Insecure: cfg.Insecure,
PSK: cfg.PSK,
ConnectionGater: cfg.ConnectionGater,
Reporter: cfg.Reporter,
PeerKey: autonatPrivKey,
Peerstore: ps,
DialRanker: swarm.NoDelayDialRanker,
UDPBlackHoleSuccessCounter: cfg.UDPBlackHoleSuccessCounter,
IPv6BlackHoleSuccessCounter: cfg.IPv6BlackHoleSuccessCounter,
ResourceManager: cfg.ResourceManager,
SwarmOpts: []swarm.Option{
// Don't update black hole state for failed autonat dials
swarm.WithReadOnlyBlackHoleDetector(),
},
}
fxopts, err := autoNatCfg.addTransports()
if err != nil {
return nil, err
}
var dialerHost host.Host
fxopts = append(fxopts,
fx.Provide(eventbus.NewBus),
fx.Provide(func(lifecycle fx.Lifecycle, b event.Bus) (*swarm.Swarm, error) {
lifecycle.Append(fx.Hook{
OnStop: func(context.Context) error {
return ps.Close()
}})
sw, err := autoNatCfg.makeSwarm(b, false)
return sw, err
}),
fx.Provide(func(sw *swarm.Swarm) *blankhost.BlankHost {
return blankhost.NewBlankHost(sw)
}),
fx.Provide(func(bh *blankhost.BlankHost) host.Host {
return bh
}),
fx.Provide(func() crypto.PrivKey { return autonatPrivKey }),
fx.Provide(func(bh host.Host) peer.ID { return bh.ID() }),
fx.Invoke(func(bh *blankhost.BlankHost) {
dialerHost = bh
}),
)
app := fx.New(fxopts...)
if err := app.Err(); err != nil {
return nil, err
}
err = app.Start(context.Background())
if err != nil {
return nil, err
}
go func() {
<-dialerHost.Network().(*swarm.Swarm).Done()
app.Stop(context.Background())
}()
return dialerHost, nil
}

func (cfg *Config) addTransports() ([]fx.Option, error) {
fxopts := []fx.Option{
fx.WithLogger(func() fxevent.Logger { return getFXLogger() }),
Expand Down Expand Up @@ -291,6 +373,14 @@ func (cfg *Config) addTransports() ([]fx.Option, error) {
}

func (cfg *Config) newBasicHost(swrm *swarm.Swarm, eventBus event.Bus) (*bhost.BasicHost, error) {
var autonatv2Dialer host.Host
if cfg.EnableAutoNATv2 {
ah, err := cfg.makeAutoNATV2Host()
if err != nil {
return nil, err
}
autonatv2Dialer = ah
}
h, err := bhost.NewHost(swrm, &bhost.HostOpts{
EventBus: eventBus,
ConnManager: cfg.ConnManager,
Expand All @@ -306,6 +396,8 @@ func (cfg *Config) newBasicHost(swrm *swarm.Swarm, eventBus event.Bus) (*bhost.B
EnableMetrics: !cfg.DisableMetrics,
PrometheusRegisterer: cfg.PrometheusRegisterer,
DisableIdentifyAddressDiscovery: cfg.DisableIdentifyAddressDiscovery,
EnableAutoNATv2: cfg.EnableAutoNATv2,
AutoNATv2Dialer: autonatv2Dialer,
})
if err != nil {
return nil, err
Expand Down Expand Up @@ -488,9 +580,8 @@ func (cfg *Config) addAutoNAT(h *bhost.BasicHost) error {
Peerstore: ps,
DialRanker: swarm.NoDelayDialRanker,
SwarmOpts: []swarm.Option{
// It is better to disable black hole detection and just attempt a dial for autonat
swarm.WithUDPBlackHoleConfig(false, 0, 0),
swarm.WithIPv6BlackHoleConfig(false, 0, 0),
swarm.WithUDPBlackHoleSuccessCounter(nil),
swarm.WithIPv6BlackHoleSuccessCounter(nil),
},
}

Expand Down
3 changes: 3 additions & 0 deletions core/network/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,9 @@ type Dialer interface {
// Notify/StopNotify register and unregister a notifiee for signals
Notify(Notifiee)
StopNotify(Notifiee)

// CanDial returns whether the dialer can dial peer p at addr
CanDial(p peer.ID, addr ma.Multiaddr) bool
}

// AddrDelay provides an address along with the delay after which the address
Expand Down
25 changes: 25 additions & 0 deletions defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
rcmgr "github.com/libp2p/go-libp2p/p2p/host/resource-manager"
"github.com/libp2p/go-libp2p/p2p/muxer/yamux"
"github.com/libp2p/go-libp2p/p2p/net/connmgr"
"github.com/libp2p/go-libp2p/p2p/net/swarm"
"github.com/libp2p/go-libp2p/p2p/security/noise"
tls "github.com/libp2p/go-libp2p/p2p/security/tls"
quic "github.com/libp2p/go-libp2p/p2p/transport/quic"
Expand Down Expand Up @@ -133,6 +134,18 @@ var DefaultPrometheusRegisterer = func(cfg *Config) error {
return cfg.Apply(PrometheusRegisterer(prometheus.DefaultRegisterer))
}

var defaultUDPBlackHoleDetector = func(cfg *Config) error {
// A black hole is a binary property. On a network if UDP dials are blocked, all dials will
// fail. So a low success rate of 5 out 100 dials is good enough.
return cfg.Apply(UDPBlackHoleSuccessCounter(&swarm.BlackHoleSuccessCounter{N: 100, MinSuccesses: 5, Name: "UDP"}))
}

var defaultIPv6BlackHoleDetector = func(cfg *Config) error {
// A black hole is a binary property. On a network if there is no IPv6 connectivity, all
// dials will fail. So a low success rate of 5 out 100 dials is good enough.
return cfg.Apply(IPv6BlackHoleSuccessCounter(&swarm.BlackHoleSuccessCounter{N: 100, MinSuccesses: 5, Name: "IPv6"}))
}

// Complete list of default options and when to fallback on them.
//
// Please *DON'T* specify default options any other way. Putting this all here
Expand Down Expand Up @@ -189,6 +202,18 @@ var defaults = []struct {
fallback: func(cfg *Config) bool { return !cfg.DisableMetrics && cfg.PrometheusRegisterer == nil },
opt: DefaultPrometheusRegisterer,
},
{
fallback: func(cfg *Config) bool {
return !cfg.CustomUDPBlackHoleSuccessCounter && cfg.UDPBlackHoleSuccessCounter == nil
},
opt: defaultUDPBlackHoleDetector,
},
{
fallback: func(cfg *Config) bool {
return !cfg.CustomIPv6BlackHoleSuccessCounter && cfg.IPv6BlackHoleSuccessCounter == nil
},
opt: defaultIPv6BlackHoleDetector,
},
}

// Defaults configures libp2p to use the default options. Can be combined with
Expand Down
6 changes: 6 additions & 0 deletions libp2p_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,12 @@ func TestInsecureConstructor(t *testing.T) {
h.Close()
}

func TestAutoNATv2Service(t *testing.T) {
h, err := New(EnableAutoNATv2())
require.NoError(t, err)
h.Close()
}

func TestDisableIdentifyAddressDiscovery(t *testing.T) {
h, err := New(DisableIdentifyAddressDiscovery())
require.NoError(t, err)
Expand Down
26 changes: 26 additions & 0 deletions options.go
Original file line number Diff line number Diff line change
Expand Up @@ -609,3 +609,29 @@ func DisableIdentifyAddressDiscovery() Option {
return nil
}
}

// EnableAutoNATv2 enables autonat v2
func EnableAutoNATv2() Option {
return func(cfg *Config) error {
cfg.EnableAutoNATv2 = true
return nil
}
}

// UDPBlackHoleSuccessCounter configures libp2p to use f as the black hole filter for UDP addrs
func UDPBlackHoleSuccessCounter(f *swarm.BlackHoleSuccessCounter) Option {
return func(cfg *Config) error {
cfg.UDPBlackHoleSuccessCounter = f
cfg.CustomUDPBlackHoleSuccessCounter = true
return nil
}
}

// IPv6BlackHoleSuccessCounter configures libp2p to use f as the black hole filter for IPv6 addrs
func IPv6BlackHoleSuccessCounter(f *swarm.BlackHoleSuccessCounter) Option {
return func(cfg *Config) error {
cfg.IPv6BlackHoleSuccessCounter = f
cfg.CustomIPv6BlackHoleSuccessCounter = true
return nil
}
}
21 changes: 21 additions & 0 deletions p2p/host/basic/basic_host.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"github.com/libp2p/go-libp2p/p2p/host/eventbus"
"github.com/libp2p/go-libp2p/p2p/host/pstoremanager"
"github.com/libp2p/go-libp2p/p2p/host/relaysvc"
"github.com/libp2p/go-libp2p/p2p/protocol/autonatv2"
relayv2 "github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/relay"
"github.com/libp2p/go-libp2p/p2p/protocol/holepunch"
"github.com/libp2p/go-libp2p/p2p/protocol/identify"
Expand Down Expand Up @@ -105,6 +106,8 @@ type BasicHost struct {
caBook peerstore.CertifiedAddrBook

autoNat autonat.AutoNAT

autonatv2 *autonatv2.AutoNAT
}

var _ host.Host = (*BasicHost)(nil)
Expand Down Expand Up @@ -167,6 +170,8 @@ type HostOpts struct {

// DisableIdentifyAddressDiscovery disables address discovery using peer provided observed addresses in identify
DisableIdentifyAddressDiscovery bool
EnableAutoNATv2 bool
AutoNATv2Dialer host.Host
}

// NewHost constructs a new *BasicHost and activates it by attaching its stream and connection handlers to the given inet.Network.
Expand Down Expand Up @@ -310,6 +315,13 @@ func NewHost(n network.Network, opts *HostOpts) (*BasicHost, error) {
h.pings = ping.NewPingService(h)
}

if opts.EnableAutoNATv2 {
h.autonatv2, err = autonatv2.New(h, opts.AutoNATv2Dialer)
if err != nil {
return nil, fmt.Errorf("failed to create autonatv2: %w", err)
}
}

n.SetStreamHandler(h.newStreamHandler)

// register to be notified when the network's listen addrs change,
Expand Down Expand Up @@ -398,6 +410,12 @@ func (h *BasicHost) Start() {
h.psManager.Start()
h.refCount.Add(1)
h.ids.Start()
if h.autonatv2 != nil {
err := h.autonatv2.Start()
if err != nil {
log.Errorf("autonat v2 failed to start: %s", err)
}
}
go h.background()
}

Expand Down Expand Up @@ -1100,6 +1118,9 @@ func (h *BasicHost) Close() error {
if h.hps != nil {
h.hps.Close()
}
if h.autonatv2 != nil {
h.autonatv2.Close()
}

_ = h.emitters.evtLocalProtocolsUpdated.Close()
_ = h.emitters.evtLocalAddrsUpdated.Close()
Expand Down
4 changes: 4 additions & 0 deletions p2p/net/mock/mock_peernet.go
Original file line number Diff line number Diff line change
Expand Up @@ -434,3 +434,7 @@ func (pn *peernet) notifyAll(notification func(f network.Notifiee)) {
func (pn *peernet) ResourceManager() network.ResourceManager {
return &network.NullResourceManager{}
}

func (pn *peernet) CanDial(p peer.ID, addr ma.Multiaddr) bool {
return true
}
Loading

0 comments on commit 87c4355

Please sign in to comment.