diff --git a/config/config.go b/config/config.go index 2791ce4309..0a1af32a0f 100644 --- a/config/config.go +++ b/config/config.go @@ -572,7 +572,11 @@ func (cfg *Config) NewNode() (host.Host, error) { if !cfg.DisableMetrics { mt = autonatv2.NewMetricsTracer(cfg.PrometheusRegisterer) } - autoNATv2, err := autonatv2.New(ah, autonatv2.WithMetricsTracer(mt)) + opts := []autonatv2.AutoNATOption{autonatv2.WithMetricsTracer(mt)} + if cfg.AutoNATConfig.ForceReachability != nil { + opts = append(opts, autonatv2.WithReachability(*cfg.AutoNATConfig.ForceReachability)) + } + autoNATv2, err := autonatv2.New(ah, opts...) if err != nil { return nil, fmt.Errorf("failed to create autonatv2: %w", err) } diff --git a/libp2p_test.go b/libp2p_test.go index d9ca422c2c..727b8f12f2 100644 --- a/libp2p_test.go +++ b/libp2p_test.go @@ -423,6 +423,24 @@ func TestAutoNATv2Service(t *testing.T) { h.Close() } +func TestAutoNATv2ForcedReachability(t *testing.T) { + client, err := New( + EnableAutoNATv2(), + ForceReachabilityPrivate(), + ) + require.NoError(t, err) + defer client.Close() + + confirmedAddrsHost, ok := client.(interface { + ConfirmedAddrs() (reachable, unreachable, unknown []ma.Multiaddr) + }) + require.True(t, ok) + require.Eventually(t, func() bool { + reachable, unreachable, unknown := confirmedAddrsHost.ConfirmedAddrs() + return len(reachable) == 0 && len(unreachable) > 0 && len(unknown) == 0 + }, 10*time.Second, 50*time.Millisecond) +} + func TestDisableIdentifyAddressDiscovery(t *testing.T) { h, err := New(DisableIdentifyAddressDiscovery()) require.NoError(t, err) diff --git a/p2p/protocol/autonatv2/autonat.go b/p2p/protocol/autonatv2/autonat.go index 95f78c9329..a2a1f887a2 100644 --- a/p2p/protocol/autonatv2/autonat.go +++ b/p2p/protocol/autonatv2/autonat.go @@ -90,6 +90,7 @@ type AutoNAT struct { // allowPrivateAddrs enables using private and localhost addresses for reachability checks. // This is only useful for testing. allowPrivateAddrs bool + forceReachability *network.Reachability } // New returns a new AutoNAT instance. @@ -110,6 +111,7 @@ func New(dialerHost host.Host, opts ...AutoNATOption) (*AutoNAT, error) { srv: newServer(dialerHost, s), cli: newClient(s), allowPrivateAddrs: s.allowPrivateAddrs, + forceReachability: s.forceReachability, peers: newPeersMap(), throttlePeer: make(map[peer.ID]time.Time), throttlePeerDuration: s.throttlePeerDuration, @@ -179,6 +181,9 @@ func (an *AutoNAT) Close() { // GetReachability makes a single dial request for checking reachability for requested addresses func (an *AutoNAT) GetReachability(ctx context.Context, reqs []Request) (Result, error) { + if an.forceReachability != nil && len(reqs) > 0 { + return Result{Addr: reqs[0].Addr, Idx: 0, Reachability: *an.forceReachability}, nil + } var filteredReqs []Request if !an.allowPrivateAddrs { filteredReqs = make([]Request, 0, len(reqs)) diff --git a/p2p/protocol/autonatv2/autonat_test.go b/p2p/protocol/autonatv2/autonat_test.go index dca55d2140..174129d385 100644 --- a/p2p/protocol/autonatv2/autonat_test.go +++ b/p2p/protocol/autonatv2/autonat_test.go @@ -97,6 +97,24 @@ func TestAutoNATPrivateAddr(t *testing.T) { require.ErrorIs(t, err, ErrPrivateAddrs) } +func TestForcedReachability(t *testing.T) { + addr := ma.StringCast("/ip4/192.168.0.1/udp/10/quic-v1") + for _, reachability := range []network.Reachability{ + network.ReachabilityPrivate, + network.ReachabilityPublic, + } { + t.Run(reachability.String(), func(t *testing.T) { + an := newAutoNAT(t, nil, WithReachability(reachability)) + defer an.Close() + defer an.host.Close() + + res, err := an.GetReachability(context.Background(), []Request{{Addr: addr}}) + require.NoError(t, err) + require.Equal(t, Result{Addr: addr, Reachability: reachability}, res) + }) + } +} + func TestClientRequest(t *testing.T) { an := newAutoNAT(t, nil, AllowPrivateAddrs) defer an.Close() diff --git a/p2p/protocol/autonatv2/options.go b/p2p/protocol/autonatv2/options.go index 6222def0c7..b91b8d7540 100644 --- a/p2p/protocol/autonatv2/options.go +++ b/p2p/protocol/autonatv2/options.go @@ -1,6 +1,10 @@ package autonatv2 -import "time" +import ( + "time" + + "github.com/libp2p/go-libp2p/core/network" +) // autoNATSettings is used to configure AutoNAT type autoNATSettings struct { @@ -14,6 +18,7 @@ type autoNATSettings struct { amplificatonAttackPreventionDialWait time.Duration metricsTracer MetricsTracer throttlePeerDuration time.Duration + forceReachability *network.Reachability } func defaultSettings() *autoNATSettings { @@ -49,6 +54,15 @@ func WithMetricsTracer(m MetricsTracer) AutoNATOption { } } +// WithReachability overrides automatic reachability detection with a fixed status. +// The AutoNAT service continues to handle requests from other peers. +func WithReachability(reachability network.Reachability) AutoNATOption { + return func(s *autoNATSettings) error { + s.forceReachability = &reachability + return nil + } +} + func withDataRequestPolicy(drp dataRequestPolicyFunc) AutoNATOption { return func(s *autoNATSettings) error { s.dataRequestPolicy = drp