Skip to content

Commit 87c4355

Browse files
authored
autonatv2: implement autonatv2 spec (#2469)
1 parent 6cebdd8 commit 87c4355

24 files changed

+3555
-173
lines changed

config/config.go

+95-4
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"github.com/libp2p/go-libp2p/p2p/host/autonat"
2525
"github.com/libp2p/go-libp2p/p2p/host/autorelay"
2626
bhost "github.com/libp2p/go-libp2p/p2p/host/basic"
27+
blankhost "github.com/libp2p/go-libp2p/p2p/host/blank"
2728
"github.com/libp2p/go-libp2p/p2p/host/eventbus"
2829
"github.com/libp2p/go-libp2p/p2p/host/peerstore/pstoremem"
2930
rcmgr "github.com/libp2p/go-libp2p/p2p/host/resource-manager"
@@ -131,6 +132,13 @@ type Config struct {
131132
SwarmOpts []swarm.Option
132133

133134
DisableIdentifyAddressDiscovery bool
135+
136+
EnableAutoNATv2 bool
137+
138+
UDPBlackHoleSuccessCounter *swarm.BlackHoleSuccessCounter
139+
CustomUDPBlackHoleSuccessCounter bool
140+
IPv6BlackHoleSuccessCounter *swarm.BlackHoleSuccessCounter
141+
CustomIPv6BlackHoleSuccessCounter bool
134142
}
135143

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

168-
opts := cfg.SwarmOpts
176+
opts := append(cfg.SwarmOpts,
177+
swarm.WithUDPBlackHoleSuccessCounter(cfg.UDPBlackHoleSuccessCounter),
178+
swarm.WithIPv6BlackHoleSuccessCounter(cfg.IPv6BlackHoleSuccessCounter),
179+
)
169180
if cfg.Reporter != nil {
170181
opts = append(opts, swarm.WithMetrics(cfg.Reporter))
171182
}
@@ -193,6 +204,77 @@ func (cfg *Config) makeSwarm(eventBus event.Bus, enableMetrics bool) (*swarm.Swa
193204
return swarm.NewSwarm(pid, cfg.Peerstore, eventBus, opts...)
194205
}
195206

207+
func (cfg *Config) makeAutoNATV2Host() (host.Host, error) {
208+
autonatPrivKey, _, err := crypto.GenerateEd25519Key(rand.Reader)
209+
if err != nil {
210+
return nil, err
211+
}
212+
ps, err := pstoremem.NewPeerstore()
213+
if err != nil {
214+
return nil, err
215+
}
216+
217+
autoNatCfg := Config{
218+
Transports: cfg.Transports,
219+
Muxers: cfg.Muxers,
220+
SecurityTransports: cfg.SecurityTransports,
221+
Insecure: cfg.Insecure,
222+
PSK: cfg.PSK,
223+
ConnectionGater: cfg.ConnectionGater,
224+
Reporter: cfg.Reporter,
225+
PeerKey: autonatPrivKey,
226+
Peerstore: ps,
227+
DialRanker: swarm.NoDelayDialRanker,
228+
UDPBlackHoleSuccessCounter: cfg.UDPBlackHoleSuccessCounter,
229+
IPv6BlackHoleSuccessCounter: cfg.IPv6BlackHoleSuccessCounter,
230+
ResourceManager: cfg.ResourceManager,
231+
SwarmOpts: []swarm.Option{
232+
// Don't update black hole state for failed autonat dials
233+
swarm.WithReadOnlyBlackHoleDetector(),
234+
},
235+
}
236+
fxopts, err := autoNatCfg.addTransports()
237+
if err != nil {
238+
return nil, err
239+
}
240+
var dialerHost host.Host
241+
fxopts = append(fxopts,
242+
fx.Provide(eventbus.NewBus),
243+
fx.Provide(func(lifecycle fx.Lifecycle, b event.Bus) (*swarm.Swarm, error) {
244+
lifecycle.Append(fx.Hook{
245+
OnStop: func(context.Context) error {
246+
return ps.Close()
247+
}})
248+
sw, err := autoNatCfg.makeSwarm(b, false)
249+
return sw, err
250+
}),
251+
fx.Provide(func(sw *swarm.Swarm) *blankhost.BlankHost {
252+
return blankhost.NewBlankHost(sw)
253+
}),
254+
fx.Provide(func(bh *blankhost.BlankHost) host.Host {
255+
return bh
256+
}),
257+
fx.Provide(func() crypto.PrivKey { return autonatPrivKey }),
258+
fx.Provide(func(bh host.Host) peer.ID { return bh.ID() }),
259+
fx.Invoke(func(bh *blankhost.BlankHost) {
260+
dialerHost = bh
261+
}),
262+
)
263+
app := fx.New(fxopts...)
264+
if err := app.Err(); err != nil {
265+
return nil, err
266+
}
267+
err = app.Start(context.Background())
268+
if err != nil {
269+
return nil, err
270+
}
271+
go func() {
272+
<-dialerHost.Network().(*swarm.Swarm).Done()
273+
app.Stop(context.Background())
274+
}()
275+
return dialerHost, nil
276+
}
277+
196278
func (cfg *Config) addTransports() ([]fx.Option, error) {
197279
fxopts := []fx.Option{
198280
fx.WithLogger(func() fxevent.Logger { return getFXLogger() }),
@@ -291,6 +373,14 @@ func (cfg *Config) addTransports() ([]fx.Option, error) {
291373
}
292374

293375
func (cfg *Config) newBasicHost(swrm *swarm.Swarm, eventBus event.Bus) (*bhost.BasicHost, error) {
376+
var autonatv2Dialer host.Host
377+
if cfg.EnableAutoNATv2 {
378+
ah, err := cfg.makeAutoNATV2Host()
379+
if err != nil {
380+
return nil, err
381+
}
382+
autonatv2Dialer = ah
383+
}
294384
h, err := bhost.NewHost(swrm, &bhost.HostOpts{
295385
EventBus: eventBus,
296386
ConnManager: cfg.ConnManager,
@@ -306,6 +396,8 @@ func (cfg *Config) newBasicHost(swrm *swarm.Swarm, eventBus event.Bus) (*bhost.B
306396
EnableMetrics: !cfg.DisableMetrics,
307397
PrometheusRegisterer: cfg.PrometheusRegisterer,
308398
DisableIdentifyAddressDiscovery: cfg.DisableIdentifyAddressDiscovery,
399+
EnableAutoNATv2: cfg.EnableAutoNATv2,
400+
AutoNATv2Dialer: autonatv2Dialer,
309401
})
310402
if err != nil {
311403
return nil, err
@@ -488,9 +580,8 @@ func (cfg *Config) addAutoNAT(h *bhost.BasicHost) error {
488580
Peerstore: ps,
489581
DialRanker: swarm.NoDelayDialRanker,
490582
SwarmOpts: []swarm.Option{
491-
// It is better to disable black hole detection and just attempt a dial for autonat
492-
swarm.WithUDPBlackHoleConfig(false, 0, 0),
493-
swarm.WithIPv6BlackHoleConfig(false, 0, 0),
583+
swarm.WithUDPBlackHoleSuccessCounter(nil),
584+
swarm.WithIPv6BlackHoleSuccessCounter(nil),
494585
},
495586
}
496587

core/network/network.go

+3
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,9 @@ type Dialer interface {
194194
// Notify/StopNotify register and unregister a notifiee for signals
195195
Notify(Notifiee)
196196
StopNotify(Notifiee)
197+
198+
// CanDial returns whether the dialer can dial peer p at addr
199+
CanDial(p peer.ID, addr ma.Multiaddr) bool
197200
}
198201

199202
// AddrDelay provides an address along with the delay after which the address

defaults.go

+25
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
rcmgr "github.com/libp2p/go-libp2p/p2p/host/resource-manager"
1111
"github.com/libp2p/go-libp2p/p2p/muxer/yamux"
1212
"github.com/libp2p/go-libp2p/p2p/net/connmgr"
13+
"github.com/libp2p/go-libp2p/p2p/net/swarm"
1314
"github.com/libp2p/go-libp2p/p2p/security/noise"
1415
tls "github.com/libp2p/go-libp2p/p2p/security/tls"
1516
quic "github.com/libp2p/go-libp2p/p2p/transport/quic"
@@ -133,6 +134,18 @@ var DefaultPrometheusRegisterer = func(cfg *Config) error {
133134
return cfg.Apply(PrometheusRegisterer(prometheus.DefaultRegisterer))
134135
}
135136

137+
var defaultUDPBlackHoleDetector = func(cfg *Config) error {
138+
// A black hole is a binary property. On a network if UDP dials are blocked, all dials will
139+
// fail. So a low success rate of 5 out 100 dials is good enough.
140+
return cfg.Apply(UDPBlackHoleSuccessCounter(&swarm.BlackHoleSuccessCounter{N: 100, MinSuccesses: 5, Name: "UDP"}))
141+
}
142+
143+
var defaultIPv6BlackHoleDetector = func(cfg *Config) error {
144+
// A black hole is a binary property. On a network if there is no IPv6 connectivity, all
145+
// dials will fail. So a low success rate of 5 out 100 dials is good enough.
146+
return cfg.Apply(IPv6BlackHoleSuccessCounter(&swarm.BlackHoleSuccessCounter{N: 100, MinSuccesses: 5, Name: "IPv6"}))
147+
}
148+
136149
// Complete list of default options and when to fallback on them.
137150
//
138151
// Please *DON'T* specify default options any other way. Putting this all here
@@ -189,6 +202,18 @@ var defaults = []struct {
189202
fallback: func(cfg *Config) bool { return !cfg.DisableMetrics && cfg.PrometheusRegisterer == nil },
190203
opt: DefaultPrometheusRegisterer,
191204
},
205+
{
206+
fallback: func(cfg *Config) bool {
207+
return !cfg.CustomUDPBlackHoleSuccessCounter && cfg.UDPBlackHoleSuccessCounter == nil
208+
},
209+
opt: defaultUDPBlackHoleDetector,
210+
},
211+
{
212+
fallback: func(cfg *Config) bool {
213+
return !cfg.CustomIPv6BlackHoleSuccessCounter && cfg.IPv6BlackHoleSuccessCounter == nil
214+
},
215+
opt: defaultIPv6BlackHoleDetector,
216+
},
192217
}
193218

194219
// Defaults configures libp2p to use the default options. Can be combined with

libp2p_test.go

+6
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,12 @@ func TestInsecureConstructor(t *testing.T) {
397397
h.Close()
398398
}
399399

400+
func TestAutoNATv2Service(t *testing.T) {
401+
h, err := New(EnableAutoNATv2())
402+
require.NoError(t, err)
403+
h.Close()
404+
}
405+
400406
func TestDisableIdentifyAddressDiscovery(t *testing.T) {
401407
h, err := New(DisableIdentifyAddressDiscovery())
402408
require.NoError(t, err)

options.go

+26
Original file line numberDiff line numberDiff line change
@@ -609,3 +609,29 @@ func DisableIdentifyAddressDiscovery() Option {
609609
return nil
610610
}
611611
}
612+
613+
// EnableAutoNATv2 enables autonat v2
614+
func EnableAutoNATv2() Option {
615+
return func(cfg *Config) error {
616+
cfg.EnableAutoNATv2 = true
617+
return nil
618+
}
619+
}
620+
621+
// UDPBlackHoleSuccessCounter configures libp2p to use f as the black hole filter for UDP addrs
622+
func UDPBlackHoleSuccessCounter(f *swarm.BlackHoleSuccessCounter) Option {
623+
return func(cfg *Config) error {
624+
cfg.UDPBlackHoleSuccessCounter = f
625+
cfg.CustomUDPBlackHoleSuccessCounter = true
626+
return nil
627+
}
628+
}
629+
630+
// IPv6BlackHoleSuccessCounter configures libp2p to use f as the black hole filter for IPv6 addrs
631+
func IPv6BlackHoleSuccessCounter(f *swarm.BlackHoleSuccessCounter) Option {
632+
return func(cfg *Config) error {
633+
cfg.IPv6BlackHoleSuccessCounter = f
634+
cfg.CustomIPv6BlackHoleSuccessCounter = true
635+
return nil
636+
}
637+
}

p2p/host/basic/basic_host.go

+21
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"github.com/libp2p/go-libp2p/p2p/host/eventbus"
2525
"github.com/libp2p/go-libp2p/p2p/host/pstoremanager"
2626
"github.com/libp2p/go-libp2p/p2p/host/relaysvc"
27+
"github.com/libp2p/go-libp2p/p2p/protocol/autonatv2"
2728
relayv2 "github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/relay"
2829
"github.com/libp2p/go-libp2p/p2p/protocol/holepunch"
2930
"github.com/libp2p/go-libp2p/p2p/protocol/identify"
@@ -105,6 +106,8 @@ type BasicHost struct {
105106
caBook peerstore.CertifiedAddrBook
106107

107108
autoNat autonat.AutoNAT
109+
110+
autonatv2 *autonatv2.AutoNAT
108111
}
109112

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

168171
// DisableIdentifyAddressDiscovery disables address discovery using peer provided observed addresses in identify
169172
DisableIdentifyAddressDiscovery bool
173+
EnableAutoNATv2 bool
174+
AutoNATv2Dialer host.Host
170175
}
171176

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

318+
if opts.EnableAutoNATv2 {
319+
h.autonatv2, err = autonatv2.New(h, opts.AutoNATv2Dialer)
320+
if err != nil {
321+
return nil, fmt.Errorf("failed to create autonatv2: %w", err)
322+
}
323+
}
324+
313325
n.SetStreamHandler(h.newStreamHandler)
314326

315327
// register to be notified when the network's listen addrs change,
@@ -398,6 +410,12 @@ func (h *BasicHost) Start() {
398410
h.psManager.Start()
399411
h.refCount.Add(1)
400412
h.ids.Start()
413+
if h.autonatv2 != nil {
414+
err := h.autonatv2.Start()
415+
if err != nil {
416+
log.Errorf("autonat v2 failed to start: %s", err)
417+
}
418+
}
401419
go h.background()
402420
}
403421

@@ -1100,6 +1118,9 @@ func (h *BasicHost) Close() error {
11001118
if h.hps != nil {
11011119
h.hps.Close()
11021120
}
1121+
if h.autonatv2 != nil {
1122+
h.autonatv2.Close()
1123+
}
11031124

11041125
_ = h.emitters.evtLocalProtocolsUpdated.Close()
11051126
_ = h.emitters.evtLocalAddrsUpdated.Close()

p2p/net/mock/mock_peernet.go

+4
Original file line numberDiff line numberDiff line change
@@ -434,3 +434,7 @@ func (pn *peernet) notifyAll(notification func(f network.Notifiee)) {
434434
func (pn *peernet) ResourceManager() network.ResourceManager {
435435
return &network.NullResourceManager{}
436436
}
437+
438+
func (pn *peernet) CanDial(p peer.ID, addr ma.Multiaddr) bool {
439+
return true
440+
}

0 commit comments

Comments
 (0)