Skip to content

Commit 3afc95f

Browse files
committed
autonatv2: implement specs
1 parent 0385ec9 commit 3afc95f

18 files changed

+2861
-5
lines changed

config/config.go

+83
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"
@@ -129,6 +130,8 @@ type Config struct {
129130
DialRanker network.DialRanker
130131

131132
SwarmOpts []swarm.Option
133+
134+
DisableAutoNATv2 bool
132135
}
133136

134137
func (cfg *Config) makeSwarm(eventBus event.Bus, enableMetrics bool) (*swarm.Swarm, error) {
@@ -191,6 +194,76 @@ func (cfg *Config) makeSwarm(eventBus event.Bus, enableMetrics bool) (*swarm.Swa
191194
return swarm.NewSwarm(pid, cfg.Peerstore, eventBus, opts...)
192195
}
193196

197+
func (cfg *Config) makeAutoNATHost() (host.Host, error) {
198+
autonatPrivKey, _, err := crypto.GenerateEd25519Key(rand.Reader)
199+
if err != nil {
200+
return nil, err
201+
}
202+
ps, err := pstoremem.NewPeerstore()
203+
if err != nil {
204+
return nil, err
205+
}
206+
207+
autoNatCfg := Config{
208+
Transports: cfg.Transports,
209+
Muxers: cfg.Muxers,
210+
SecurityTransports: cfg.SecurityTransports,
211+
Insecure: cfg.Insecure,
212+
PSK: cfg.PSK,
213+
ConnectionGater: cfg.ConnectionGater,
214+
Reporter: cfg.Reporter,
215+
PeerKey: autonatPrivKey,
216+
Peerstore: ps,
217+
DialRanker: swarm.NoDelayDialRanker,
218+
SwarmOpts: []swarm.Option{
219+
// Disable black hole detection on autonat dialers
220+
// It is better to attempt a dial and fail for AutoNAT use cases
221+
swarm.WithUDPBlackHoleConfig(false, 0, 0),
222+
swarm.WithIPv6BlackHoleConfig(false, 0, 0),
223+
},
224+
}
225+
fxopts, err := autoNatCfg.addTransports()
226+
if err != nil {
227+
return nil, err
228+
}
229+
var dialerHost host.Host
230+
fxopts = append(fxopts,
231+
fx.Provide(eventbus.NewBus),
232+
fx.Provide(func(lifecycle fx.Lifecycle, b event.Bus) (*swarm.Swarm, error) {
233+
lifecycle.Append(fx.Hook{
234+
OnStop: func(context.Context) error {
235+
return ps.Close()
236+
}})
237+
sw, err := autoNatCfg.makeSwarm(b, false)
238+
return sw, err
239+
}),
240+
fx.Provide(func(sw *swarm.Swarm) *blankhost.BlankHost {
241+
return blankhost.NewBlankHost(sw)
242+
}),
243+
fx.Provide(func(bh *blankhost.BlankHost) host.Host {
244+
return bh
245+
}),
246+
fx.Provide(func() crypto.PrivKey { return autonatPrivKey }),
247+
fx.Provide(func(bh host.Host) peer.ID { return bh.ID() }),
248+
fx.Invoke(func(bh *blankhost.BlankHost) {
249+
dialerHost = bh
250+
}),
251+
)
252+
app := fx.New(fxopts...)
253+
if err := app.Err(); err != nil {
254+
return nil, err
255+
}
256+
err = app.Start(context.Background())
257+
if err != nil {
258+
return nil, err
259+
}
260+
go func() {
261+
<-dialerHost.Network().(*swarm.Swarm).Done()
262+
app.Stop(context.Background())
263+
}()
264+
return dialerHost, nil
265+
}
266+
194267
func (cfg *Config) addTransports() ([]fx.Option, error) {
195268
fxopts := []fx.Option{
196269
fx.WithLogger(func() fxevent.Logger { return getFXLogger() }),
@@ -289,6 +362,14 @@ func (cfg *Config) addTransports() ([]fx.Option, error) {
289362
}
290363

291364
func (cfg *Config) newBasicHost(swrm *swarm.Swarm, eventBus event.Bus) (*bhost.BasicHost, error) {
365+
var autonatv2Dialer host.Host
366+
if !cfg.DisableAutoNATv2 {
367+
ah, err := cfg.makeAutoNATHost()
368+
if err != nil {
369+
return nil, err
370+
}
371+
autonatv2Dialer = ah
372+
}
292373
h, err := bhost.NewHost(swrm, &bhost.HostOpts{
293374
EventBus: eventBus,
294375
ConnManager: cfg.ConnManager,
@@ -303,6 +384,8 @@ func (cfg *Config) newBasicHost(swrm *swarm.Swarm, eventBus event.Bus) (*bhost.B
303384
RelayServiceOpts: cfg.RelayServiceOpts,
304385
EnableMetrics: !cfg.DisableMetrics,
305386
PrometheusRegisterer: cfg.PrometheusRegisterer,
387+
EnableAutoNATv2: !cfg.DisableAutoNATv2,
388+
AutoNATv2Dialer: autonatv2Dialer,
306389
})
307390
if err != nil {
308391
return nil, err

core/network/network.go

+17
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,20 @@ type Network interface {
152152
ResourceManager() ResourceManager
153153
}
154154

155+
// Dialability indicates how dialable a (peer, addr) combination is.
156+
type Dialability int
157+
158+
const (
159+
// DialabilityUnknown indicates that the dialer cannot dial the (peer, addr) combination
160+
DialabilityUnknown Dialability = iota
161+
162+
// DialabilityDialable indicates that the dialer can dial the (peer, addr) combination
163+
DialabilityDialable
164+
165+
// DialabilityUndialable indicates that the dialer cannot dial the (peer, addr) combinaton
166+
DialabilityUndialable
167+
)
168+
155169
// Dialer represents a service that can dial out to peers
156170
// (this is usually just a Network, but other services may not need the whole
157171
// stack, and thus it becomes easier to mock)
@@ -185,6 +199,9 @@ type Dialer interface {
185199
// Notify/StopNotify register and unregister a notifiee for signals
186200
Notify(Notifiee)
187201
StopNotify(Notifiee)
202+
203+
// CanDial returns whether the dialer can dial peer p at addr
204+
CanDial(p peer.ID, addr ma.Multiaddr) Dialability
188205
}
189206

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

options.go

+8
Original file line numberDiff line numberDiff line change
@@ -598,3 +598,11 @@ func SwarmOpts(opts ...swarm.Option) Option {
598598
return nil
599599
}
600600
}
601+
602+
// DisableAutoNATv2 disables autonat
603+
func DisableAutoNATv2() Option {
604+
return func(cfg *Config) error {
605+
cfg.DisableAutoNATv2 = true
606+
return nil
607+
}
608+
}

p2p/host/basic/basic_host.go

+16
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"github.com/libp2p/go-libp2p/p2p/host/eventbus"
2424
"github.com/libp2p/go-libp2p/p2p/host/pstoremanager"
2525
"github.com/libp2p/go-libp2p/p2p/host/relaysvc"
26+
"github.com/libp2p/go-libp2p/p2p/protocol/autonatv2"
2627
relayv2 "github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/relay"
2728
"github.com/libp2p/go-libp2p/p2p/protocol/holepunch"
2829
"github.com/libp2p/go-libp2p/p2p/protocol/identify"
@@ -102,6 +103,8 @@ type BasicHost struct {
102103
caBook peerstore.CertifiedAddrBook
103104

104105
autoNat autonat.AutoNAT
106+
107+
autonatv2 *autonatv2.AutoNAT
105108
}
106109

107110
var _ host.Host = (*BasicHost)(nil)
@@ -161,6 +164,9 @@ type HostOpts struct {
161164
EnableMetrics bool
162165
// PrometheusRegisterer is the PrometheusRegisterer used for metrics
163166
PrometheusRegisterer prometheus.Registerer
167+
168+
EnableAutoNATv2 bool
169+
AutoNATv2Dialer host.Host
164170
}
165171

166172
// NewHost constructs a new *BasicHost and activates it by attaching its stream and connection handlers to the given inet.Network.
@@ -301,6 +307,13 @@ func NewHost(n network.Network, opts *HostOpts) (*BasicHost, error) {
301307
h.pings = ping.NewPingService(h)
302308
}
303309

310+
if opts.EnableAutoNATv2 {
311+
h.autonatv2, err = autonatv2.New(h, opts.AutoNATv2Dialer)
312+
if err != nil {
313+
return nil, fmt.Errorf("failed to create autonatv2: %w", err)
314+
}
315+
}
316+
304317
n.SetStreamHandler(h.newStreamHandler)
305318

306319
// register to be notified when the network's listen addrs change,
@@ -1029,6 +1042,9 @@ func (h *BasicHost) Close() error {
10291042
if h.hps != nil {
10301043
h.hps.Close()
10311044
}
1045+
if h.autonatv2 != nil {
1046+
h.autonatv2.Close()
1047+
}
10321048

10331049
_ = h.emitters.evtLocalProtocolsUpdated.Close()
10341050
_ = h.emitters.evtLocalAddrsUpdated.Close()

p2p/host/blank/blank.go

+4-3
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,10 @@ func NewBlankHost(n network.Network, options ...Option) *BlankHost {
6363
}
6464

6565
bh := &BlankHost{
66-
n: n,
67-
cmgr: cfg.cmgr,
68-
mux: mstream.NewMultistreamMuxer[protocol.ID](),
66+
n: n,
67+
cmgr: cfg.cmgr,
68+
mux: mstream.NewMultistreamMuxer[protocol.ID](),
69+
eventbus: cfg.eventBus,
6970
}
7071
if bh.eventbus == nil {
7172
bh.eventbus = eventbus.NewBus(eventbus.WithMetricsTracer(eventbus.NewMetricsTracer()))

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) network.Dialability {
439+
return network.DialabilityUnknown
440+
}

p2p/net/swarm/black_hole_detector.go

+28
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,13 @@ func (b *blackHoleFilter) updateState() {
144144
}
145145
}
146146

147+
func (b *blackHoleFilter) State() blackHoleState {
148+
b.mu.Lock()
149+
defer b.mu.Unlock()
150+
151+
return b.state
152+
}
153+
147154
func (b *blackHoleFilter) trackMetrics() {
148155
if b.metricsTracer == nil {
149156
return
@@ -244,6 +251,27 @@ func (d *blackHoleDetector) RecordResult(addr ma.Multiaddr, success bool) {
244251
}
245252
}
246253

254+
func (d *blackHoleDetector) State(addr ma.Multiaddr) blackHoleState {
255+
if !manet.IsPublicAddr(addr) {
256+
return blackHoleStateAllowed
257+
}
258+
259+
if d.udp != nil && isProtocolAddr(addr, ma.P_UDP) {
260+
udpState := d.udp.State()
261+
if udpState != blackHoleStateAllowed {
262+
return udpState
263+
}
264+
}
265+
266+
if d.ipv6 != nil && isProtocolAddr(addr, ma.P_IP6) {
267+
ipv6State := d.ipv6.State()
268+
if ipv6State != blackHoleStateAllowed {
269+
return ipv6State
270+
}
271+
}
272+
return blackHoleStateAllowed
273+
}
274+
247275
// blackHoleConfig is the config used for black hole detection
248276
type blackHoleConfig struct {
249277
// Enabled enables black hole detection

p2p/net/swarm/black_hole_detector_test.go

+12
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ func TestBlackHoleFilterReset(t *testing.T) {
1717
if bhf.HandleRequest() != blackHoleResultProbing {
1818
t.Fatalf("expected calls up to n to be probes")
1919
}
20+
if bhf.State() != blackHoleStateProbing {
21+
t.Fatalf("expected state to be probing got %s", bhf.State())
22+
}
2023
bhf.RecordResult(false)
2124
}
2225

@@ -26,6 +29,9 @@ func TestBlackHoleFilterReset(t *testing.T) {
2629
if (i%n == 0 && result != blackHoleResultProbing) || (i%n != 0 && result != blackHoleResultBlocked) {
2730
t.Fatalf("expected every nth dial to be a probe")
2831
}
32+
if bhf.State() != blackHoleStateBlocked {
33+
t.Fatalf("expected state to be blocked, got %s", bhf.State())
34+
}
2935
}
3036

3137
bhf.RecordResult(true)
@@ -34,12 +40,18 @@ func TestBlackHoleFilterReset(t *testing.T) {
3440
if bhf.HandleRequest() != blackHoleResultProbing {
3541
t.Fatalf("expected black hole detector state to reset after success")
3642
}
43+
if bhf.State() != blackHoleStateProbing {
44+
t.Fatalf("expected state to be probing got %s", bhf.State())
45+
}
3746
bhf.RecordResult(false)
3847
}
3948

4049
// next call should be blocked
4150
if bhf.HandleRequest() != blackHoleResultBlocked {
4251
t.Fatalf("expected dial to be blocked")
52+
if bhf.State() != blackHoleStateBlocked {
53+
t.Fatalf("expected state to be blocked, got %s", bhf.State())
54+
}
4355
}
4456
}
4557

p2p/net/swarm/swarm.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ func WithDialRanker(d network.DialRanker) Option {
112112
}
113113
}
114114

115-
// WithUDPBlackHoleConfig configures swarm to use c as the config for UDP black hole detection
115+
// WithUDPBlackHoleConfig configures swarm to use the provided config for UDP black hole detection
116116
// n is the size of the sliding window used to evaluate black hole state
117117
// min is the minimum number of successes out of n required to not block requests
118118
func WithUDPBlackHoleConfig(enabled bool, n, min int) Option {
@@ -122,7 +122,7 @@ func WithUDPBlackHoleConfig(enabled bool, n, min int) Option {
122122
}
123123
}
124124

125-
// WithIPv6BlackHoleConfig configures swarm to use c as the config for IPv6 black hole detection
125+
// WithIPv6BlackHoleConfig configures swarm to use the provided config for IPv6 black hole detection
126126
// n is the size of the sliding window used to evaluate black hole state
127127
// min is the minimum number of successes out of n required to not block requests
128128
func WithIPv6BlackHoleConfig(enabled bool, n, min int) Option {

p2p/net/swarm/swarm_dial.go

+20
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,26 @@ func (s *Swarm) dialNextAddr(ctx context.Context, p peer.ID, addr ma.Multiaddr,
416416
return nil
417417
}
418418

419+
func (s *Swarm) CanDial(p peer.ID, addr ma.Multiaddr) network.Dialability {
420+
dialable, _ := s.filterKnownUndialables(p, []ma.Multiaddr{addr})
421+
if len(dialable) == 0 {
422+
return network.DialabilityUndialable
423+
}
424+
425+
bhState := s.bhd.State(addr)
426+
switch bhState {
427+
case blackHoleStateAllowed:
428+
return network.DialabilityDialable
429+
case blackHoleStateProbing:
430+
return network.DialabilityUnknown
431+
case blackHoleStateBlocked:
432+
return network.DialabilityUndialable
433+
default:
434+
log.Errorf("SWARM BUG: unhandled black hole state for dilability check %s", bhState)
435+
return network.DialabilityUnknown
436+
}
437+
}
438+
419439
func (s *Swarm) nonProxyAddr(addr ma.Multiaddr) bool {
420440
t := s.TransportForDialing(addr)
421441
return !t.Proxy()

0 commit comments

Comments
 (0)