mirror of
https://github.com/libp2p/go-libp2p.git
synced 2025-09-27 04:26:41 +08:00
autonatv2: implement autonatv2 spec (#2469)
This commit is contained in:
@@ -24,6 +24,7 @@ import (
|
|||||||
"github.com/libp2p/go-libp2p/p2p/host/autonat"
|
"github.com/libp2p/go-libp2p/p2p/host/autonat"
|
||||||
"github.com/libp2p/go-libp2p/p2p/host/autorelay"
|
"github.com/libp2p/go-libp2p/p2p/host/autorelay"
|
||||||
bhost "github.com/libp2p/go-libp2p/p2p/host/basic"
|
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/eventbus"
|
||||||
"github.com/libp2p/go-libp2p/p2p/host/peerstore/pstoremem"
|
"github.com/libp2p/go-libp2p/p2p/host/peerstore/pstoremem"
|
||||||
rcmgr "github.com/libp2p/go-libp2p/p2p/host/resource-manager"
|
rcmgr "github.com/libp2p/go-libp2p/p2p/host/resource-manager"
|
||||||
@@ -131,6 +132,13 @@ type Config struct {
|
|||||||
SwarmOpts []swarm.Option
|
SwarmOpts []swarm.Option
|
||||||
|
|
||||||
DisableIdentifyAddressDiscovery bool
|
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) {
|
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
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
opts := cfg.SwarmOpts
|
opts := append(cfg.SwarmOpts,
|
||||||
|
swarm.WithUDPBlackHoleSuccessCounter(cfg.UDPBlackHoleSuccessCounter),
|
||||||
|
swarm.WithIPv6BlackHoleSuccessCounter(cfg.IPv6BlackHoleSuccessCounter),
|
||||||
|
)
|
||||||
if cfg.Reporter != nil {
|
if cfg.Reporter != nil {
|
||||||
opts = append(opts, swarm.WithMetrics(cfg.Reporter))
|
opts = append(opts, swarm.WithMetrics(cfg.Reporter))
|
||||||
}
|
}
|
||||||
@@ -193,6 +204,77 @@ func (cfg *Config) makeSwarm(eventBus event.Bus, enableMetrics bool) (*swarm.Swa
|
|||||||
return swarm.NewSwarm(pid, cfg.Peerstore, eventBus, opts...)
|
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) {
|
func (cfg *Config) addTransports() ([]fx.Option, error) {
|
||||||
fxopts := []fx.Option{
|
fxopts := []fx.Option{
|
||||||
fx.WithLogger(func() fxevent.Logger { return getFXLogger() }),
|
fx.WithLogger(func() fxevent.Logger { return getFXLogger() }),
|
||||||
@@ -291,6 +373,14 @@ func (cfg *Config) addTransports() ([]fx.Option, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *Config) newBasicHost(swrm *swarm.Swarm, eventBus event.Bus) (*bhost.BasicHost, 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{
|
h, err := bhost.NewHost(swrm, &bhost.HostOpts{
|
||||||
EventBus: eventBus,
|
EventBus: eventBus,
|
||||||
ConnManager: cfg.ConnManager,
|
ConnManager: cfg.ConnManager,
|
||||||
@@ -306,6 +396,8 @@ func (cfg *Config) newBasicHost(swrm *swarm.Swarm, eventBus event.Bus) (*bhost.B
|
|||||||
EnableMetrics: !cfg.DisableMetrics,
|
EnableMetrics: !cfg.DisableMetrics,
|
||||||
PrometheusRegisterer: cfg.PrometheusRegisterer,
|
PrometheusRegisterer: cfg.PrometheusRegisterer,
|
||||||
DisableIdentifyAddressDiscovery: cfg.DisableIdentifyAddressDiscovery,
|
DisableIdentifyAddressDiscovery: cfg.DisableIdentifyAddressDiscovery,
|
||||||
|
EnableAutoNATv2: cfg.EnableAutoNATv2,
|
||||||
|
AutoNATv2Dialer: autonatv2Dialer,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -488,9 +580,8 @@ func (cfg *Config) addAutoNAT(h *bhost.BasicHost) error {
|
|||||||
Peerstore: ps,
|
Peerstore: ps,
|
||||||
DialRanker: swarm.NoDelayDialRanker,
|
DialRanker: swarm.NoDelayDialRanker,
|
||||||
SwarmOpts: []swarm.Option{
|
SwarmOpts: []swarm.Option{
|
||||||
// It is better to disable black hole detection and just attempt a dial for autonat
|
swarm.WithUDPBlackHoleSuccessCounter(nil),
|
||||||
swarm.WithUDPBlackHoleConfig(false, 0, 0),
|
swarm.WithIPv6BlackHoleSuccessCounter(nil),
|
||||||
swarm.WithIPv6BlackHoleConfig(false, 0, 0),
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -194,6 +194,9 @@ type Dialer interface {
|
|||||||
// Notify/StopNotify register and unregister a notifiee for signals
|
// Notify/StopNotify register and unregister a notifiee for signals
|
||||||
Notify(Notifiee)
|
Notify(Notifiee)
|
||||||
StopNotify(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
|
// AddrDelay provides an address along with the delay after which the address
|
||||||
|
25
defaults.go
25
defaults.go
@@ -10,6 +10,7 @@ import (
|
|||||||
rcmgr "github.com/libp2p/go-libp2p/p2p/host/resource-manager"
|
rcmgr "github.com/libp2p/go-libp2p/p2p/host/resource-manager"
|
||||||
"github.com/libp2p/go-libp2p/p2p/muxer/yamux"
|
"github.com/libp2p/go-libp2p/p2p/muxer/yamux"
|
||||||
"github.com/libp2p/go-libp2p/p2p/net/connmgr"
|
"github.com/libp2p/go-libp2p/p2p/net/connmgr"
|
||||||
|
"github.com/libp2p/go-libp2p/p2p/net/swarm"
|
||||||
"github.com/libp2p/go-libp2p/p2p/security/noise"
|
"github.com/libp2p/go-libp2p/p2p/security/noise"
|
||||||
tls "github.com/libp2p/go-libp2p/p2p/security/tls"
|
tls "github.com/libp2p/go-libp2p/p2p/security/tls"
|
||||||
quic "github.com/libp2p/go-libp2p/p2p/transport/quic"
|
quic "github.com/libp2p/go-libp2p/p2p/transport/quic"
|
||||||
@@ -133,6 +134,18 @@ var DefaultPrometheusRegisterer = func(cfg *Config) error {
|
|||||||
return cfg.Apply(PrometheusRegisterer(prometheus.DefaultRegisterer))
|
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.
|
// Complete list of default options and when to fallback on them.
|
||||||
//
|
//
|
||||||
// Please *DON'T* specify default options any other way. Putting this all here
|
// Please *DON'T* specify default options any other way. Putting this all here
|
||||||
@@ -189,6 +202,18 @@ var defaults = []struct {
|
|||||||
fallback: func(cfg *Config) bool { return !cfg.DisableMetrics && cfg.PrometheusRegisterer == nil },
|
fallback: func(cfg *Config) bool { return !cfg.DisableMetrics && cfg.PrometheusRegisterer == nil },
|
||||||
opt: DefaultPrometheusRegisterer,
|
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
|
// Defaults configures libp2p to use the default options. Can be combined with
|
||||||
|
@@ -397,6 +397,12 @@ func TestInsecureConstructor(t *testing.T) {
|
|||||||
h.Close()
|
h.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAutoNATv2Service(t *testing.T) {
|
||||||
|
h, err := New(EnableAutoNATv2())
|
||||||
|
require.NoError(t, err)
|
||||||
|
h.Close()
|
||||||
|
}
|
||||||
|
|
||||||
func TestDisableIdentifyAddressDiscovery(t *testing.T) {
|
func TestDisableIdentifyAddressDiscovery(t *testing.T) {
|
||||||
h, err := New(DisableIdentifyAddressDiscovery())
|
h, err := New(DisableIdentifyAddressDiscovery())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
26
options.go
26
options.go
@@ -609,3 +609,29 @@ func DisableIdentifyAddressDiscovery() Option {
|
|||||||
return nil
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -24,6 +24,7 @@ import (
|
|||||||
"github.com/libp2p/go-libp2p/p2p/host/eventbus"
|
"github.com/libp2p/go-libp2p/p2p/host/eventbus"
|
||||||
"github.com/libp2p/go-libp2p/p2p/host/pstoremanager"
|
"github.com/libp2p/go-libp2p/p2p/host/pstoremanager"
|
||||||
"github.com/libp2p/go-libp2p/p2p/host/relaysvc"
|
"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"
|
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/holepunch"
|
||||||
"github.com/libp2p/go-libp2p/p2p/protocol/identify"
|
"github.com/libp2p/go-libp2p/p2p/protocol/identify"
|
||||||
@@ -105,6 +106,8 @@ type BasicHost struct {
|
|||||||
caBook peerstore.CertifiedAddrBook
|
caBook peerstore.CertifiedAddrBook
|
||||||
|
|
||||||
autoNat autonat.AutoNAT
|
autoNat autonat.AutoNAT
|
||||||
|
|
||||||
|
autonatv2 *autonatv2.AutoNAT
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ host.Host = (*BasicHost)(nil)
|
var _ host.Host = (*BasicHost)(nil)
|
||||||
@@ -167,6 +170,8 @@ type HostOpts struct {
|
|||||||
|
|
||||||
// DisableIdentifyAddressDiscovery disables address discovery using peer provided observed addresses in identify
|
// DisableIdentifyAddressDiscovery disables address discovery using peer provided observed addresses in identify
|
||||||
DisableIdentifyAddressDiscovery bool
|
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.
|
// 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) {
|
|||||||
h.pings = ping.NewPingService(h)
|
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)
|
n.SetStreamHandler(h.newStreamHandler)
|
||||||
|
|
||||||
// register to be notified when the network's listen addrs change,
|
// register to be notified when the network's listen addrs change,
|
||||||
@@ -398,6 +410,12 @@ func (h *BasicHost) Start() {
|
|||||||
h.psManager.Start()
|
h.psManager.Start()
|
||||||
h.refCount.Add(1)
|
h.refCount.Add(1)
|
||||||
h.ids.Start()
|
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()
|
go h.background()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1100,6 +1118,9 @@ func (h *BasicHost) Close() error {
|
|||||||
if h.hps != nil {
|
if h.hps != nil {
|
||||||
h.hps.Close()
|
h.hps.Close()
|
||||||
}
|
}
|
||||||
|
if h.autonatv2 != nil {
|
||||||
|
h.autonatv2.Close()
|
||||||
|
}
|
||||||
|
|
||||||
_ = h.emitters.evtLocalProtocolsUpdated.Close()
|
_ = h.emitters.evtLocalProtocolsUpdated.Close()
|
||||||
_ = h.emitters.evtLocalAddrsUpdated.Close()
|
_ = h.emitters.evtLocalAddrsUpdated.Close()
|
||||||
|
@@ -434,3 +434,7 @@ func (pn *peernet) notifyAll(notification func(f network.Notifiee)) {
|
|||||||
func (pn *peernet) ResourceManager() network.ResourceManager {
|
func (pn *peernet) ResourceManager() network.ResourceManager {
|
||||||
return &network.NullResourceManager{}
|
return &network.NullResourceManager{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (pn *peernet) CanDial(p peer.ID, addr ma.Multiaddr) bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
@@ -29,35 +29,26 @@ func (st blackHoleState) String() string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type blackHoleResult int
|
// BlackHoleSuccessCounter provides black hole filtering for dials. This filter should be used in concert
|
||||||
|
// with a UDP or IPv6 address filter to detect UDP or IPv6 black hole. In a black holed environment,
|
||||||
const (
|
// dial requests are refused Requests are blocked if the number of successes in the last N dials is
|
||||||
blackHoleResultAllowed blackHoleResult = iota
|
// less than MinSuccesses.
|
||||||
blackHoleResultProbing
|
// If a request succeeds in Blocked state, the filter state is reset and N subsequent requests are
|
||||||
blackHoleResultBlocked
|
// allowed before reevaluating black hole state. Dials cancelled when some other concurrent dial
|
||||||
)
|
// succeeded are counted as failures. A sufficiently large N prevents false negatives in such cases.
|
||||||
|
type BlackHoleSuccessCounter struct {
|
||||||
// blackHoleFilter provides black hole filtering for dials. This filter should be used in
|
// N is
|
||||||
// concert with a UDP of IPv6 address filter to detect UDP or IPv6 black hole. In a black
|
// 1. The minimum number of completed dials required before evaluating black hole state
|
||||||
// holed environments dial requests are blocked and only periodic probes to check the
|
// 2. the minimum number of requests after which we probe the state of the black hole in
|
||||||
// state of the black hole are allowed.
|
// blocked state
|
||||||
//
|
N int
|
||||||
// Requests are blocked if the number of successes in the last n dials is less than
|
// MinSuccesses is the minimum number of Success required in the last n dials
|
||||||
// minSuccesses. If a request succeeds in Blocked state, the filter state is reset and n
|
|
||||||
// subsequent requests are allowed before reevaluating black hole state. Dials cancelled
|
|
||||||
// when some other concurrent dial succeeded are counted as failures. A sufficiently large
|
|
||||||
// n prevents false negatives in such cases.
|
|
||||||
type blackHoleFilter struct {
|
|
||||||
// n serves the dual purpose of being the minimum number of requests after which we
|
|
||||||
// probe the state of the black hole in blocked state and the minimum number of
|
|
||||||
// completed dials required before evaluating black hole state.
|
|
||||||
n int
|
|
||||||
// minSuccesses is the minimum number of Success required in the last n dials
|
|
||||||
// to consider we are not blocked.
|
// to consider we are not blocked.
|
||||||
minSuccesses int
|
MinSuccesses int
|
||||||
// name for the detector.
|
// Name for the detector.
|
||||||
name string
|
Name string
|
||||||
|
|
||||||
|
mu sync.Mutex
|
||||||
// requests counts number of dial requests to peers. We handle request at a peer
|
// requests counts number of dial requests to peers. We handle request at a peer
|
||||||
// level and record results at individual address dial level.
|
// level and record results at individual address dial level.
|
||||||
requests int
|
requests int
|
||||||
@@ -67,22 +58,19 @@ type blackHoleFilter struct {
|
|||||||
successes int
|
successes int
|
||||||
// state is the current state of the detector
|
// state is the current state of the detector
|
||||||
state blackHoleState
|
state blackHoleState
|
||||||
|
|
||||||
mu sync.Mutex
|
|
||||||
metricsTracer MetricsTracer
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RecordResult records the outcome of a dial. A successful dial will change the state
|
// RecordResult records the outcome of a dial. A successful dial in Blocked state will change the
|
||||||
// of the filter to Allowed. A failed dial only blocks subsequent requests if the success
|
// state of the filter to Probing. A failed dial only blocks subsequent requests if the success
|
||||||
// fraction over the last n outcomes is less than the minSuccessFraction of the filter.
|
// fraction over the last n outcomes is less than the minSuccessFraction of the filter.
|
||||||
func (b *blackHoleFilter) RecordResult(success bool) {
|
func (b *BlackHoleSuccessCounter) RecordResult(success bool) {
|
||||||
b.mu.Lock()
|
b.mu.Lock()
|
||||||
defer b.mu.Unlock()
|
defer b.mu.Unlock()
|
||||||
|
|
||||||
if b.state == blackHoleStateBlocked && success {
|
if b.state == blackHoleStateBlocked && success {
|
||||||
// If the call succeeds in a blocked state we reset to allowed.
|
// If the call succeeds in a blocked state we reset to allowed.
|
||||||
// This is better than slowly accumulating values till we cross the minSuccessFraction
|
// This is better than slowly accumulating values till we cross the minSuccessFraction
|
||||||
// threshold since a blackhole is a binary property.
|
// threshold since a black hole is a binary property.
|
||||||
b.reset()
|
b.reset()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -92,7 +80,7 @@ func (b *blackHoleFilter) RecordResult(success bool) {
|
|||||||
}
|
}
|
||||||
b.dialResults = append(b.dialResults, success)
|
b.dialResults = append(b.dialResults, success)
|
||||||
|
|
||||||
if len(b.dialResults) > b.n {
|
if len(b.dialResults) > b.N {
|
||||||
if b.dialResults[0] {
|
if b.dialResults[0] {
|
||||||
b.successes--
|
b.successes--
|
||||||
}
|
}
|
||||||
@@ -100,58 +88,68 @@ func (b *blackHoleFilter) RecordResult(success bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
b.updateState()
|
b.updateState()
|
||||||
b.trackMetrics()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleRequest returns the result of applying the black hole filter for the request.
|
// HandleRequest returns the result of applying the black hole filter for the request.
|
||||||
func (b *blackHoleFilter) HandleRequest() blackHoleResult {
|
func (b *BlackHoleSuccessCounter) HandleRequest() blackHoleState {
|
||||||
b.mu.Lock()
|
b.mu.Lock()
|
||||||
defer b.mu.Unlock()
|
defer b.mu.Unlock()
|
||||||
|
|
||||||
b.requests++
|
b.requests++
|
||||||
|
|
||||||
b.trackMetrics()
|
|
||||||
|
|
||||||
if b.state == blackHoleStateAllowed {
|
if b.state == blackHoleStateAllowed {
|
||||||
return blackHoleResultAllowed
|
return blackHoleStateAllowed
|
||||||
} else if b.state == blackHoleStateProbing || b.requests%b.n == 0 {
|
} else if b.state == blackHoleStateProbing || b.requests%b.N == 0 {
|
||||||
return blackHoleResultProbing
|
return blackHoleStateProbing
|
||||||
} else {
|
} else {
|
||||||
return blackHoleResultBlocked
|
return blackHoleStateBlocked
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *blackHoleFilter) reset() {
|
func (b *BlackHoleSuccessCounter) reset() {
|
||||||
b.successes = 0
|
b.successes = 0
|
||||||
b.dialResults = b.dialResults[:0]
|
b.dialResults = b.dialResults[:0]
|
||||||
b.requests = 0
|
b.requests = 0
|
||||||
b.updateState()
|
b.updateState()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *blackHoleFilter) updateState() {
|
func (b *BlackHoleSuccessCounter) updateState() {
|
||||||
st := b.state
|
st := b.state
|
||||||
|
|
||||||
if len(b.dialResults) < b.n {
|
if len(b.dialResults) < b.N {
|
||||||
b.state = blackHoleStateProbing
|
b.state = blackHoleStateProbing
|
||||||
} else if b.successes >= b.minSuccesses {
|
} else if b.successes >= b.MinSuccesses {
|
||||||
b.state = blackHoleStateAllowed
|
b.state = blackHoleStateAllowed
|
||||||
} else {
|
} else {
|
||||||
b.state = blackHoleStateBlocked
|
b.state = blackHoleStateBlocked
|
||||||
}
|
}
|
||||||
|
|
||||||
if st != b.state {
|
if st != b.state {
|
||||||
log.Debugf("%s blackHoleDetector state changed from %s to %s", b.name, st, b.state)
|
log.Debugf("%s blackHoleDetector state changed from %s to %s", b.Name, st, b.state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *blackHoleFilter) trackMetrics() {
|
func (b *BlackHoleSuccessCounter) State() blackHoleState {
|
||||||
if b.metricsTracer == nil {
|
b.mu.Lock()
|
||||||
return
|
defer b.mu.Unlock()
|
||||||
}
|
|
||||||
|
|
||||||
nextRequestAllowedAfter := 0
|
return b.state
|
||||||
|
}
|
||||||
|
|
||||||
|
type blackHoleInfo struct {
|
||||||
|
name string
|
||||||
|
state blackHoleState
|
||||||
|
nextProbeAfter int
|
||||||
|
successFraction float64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BlackHoleSuccessCounter) info() blackHoleInfo {
|
||||||
|
b.mu.Lock()
|
||||||
|
defer b.mu.Unlock()
|
||||||
|
|
||||||
|
nextProbeAfter := 0
|
||||||
if b.state == blackHoleStateBlocked {
|
if b.state == blackHoleStateBlocked {
|
||||||
nextRequestAllowedAfter = b.n - (b.requests % b.n)
|
nextProbeAfter = b.N - (b.requests % b.N)
|
||||||
}
|
}
|
||||||
|
|
||||||
successFraction := 0.0
|
successFraction := 0.0
|
||||||
@@ -159,22 +157,27 @@ func (b *blackHoleFilter) trackMetrics() {
|
|||||||
successFraction = float64(b.successes) / float64(len(b.dialResults))
|
successFraction = float64(b.successes) / float64(len(b.dialResults))
|
||||||
}
|
}
|
||||||
|
|
||||||
b.metricsTracer.UpdatedBlackHoleFilterState(
|
return blackHoleInfo{
|
||||||
b.name,
|
name: b.Name,
|
||||||
b.state,
|
state: b.state,
|
||||||
nextRequestAllowedAfter,
|
nextProbeAfter: nextProbeAfter,
|
||||||
successFraction,
|
successFraction: successFraction,
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// blackHoleDetector provides UDP and IPv6 black hole detection using a `blackHoleFilter`
|
// blackHoleDetector provides UDP and IPv6 black hole detection using a `BlackHoleSuccessCounter` for each.
|
||||||
// for each. For details of the black hole detection logic see `blackHoleFilter`.
|
// For details of the black hole detection logic see `BlackHoleSuccessCounter`.
|
||||||
|
// In Read Only mode, detector doesn't update the state of underlying filters and refuses requests
|
||||||
|
// when black hole state is unknown. This is useful for Swarms made specifically for services like
|
||||||
|
// AutoNAT where we care about accurately reporting the reachability of a peer.
|
||||||
//
|
//
|
||||||
// black hole filtering is done at a peer dial level to ensure that periodic probes to
|
// Black hole filtering is done at a peer dial level to ensure that periodic probes to detect change
|
||||||
// detect change of the black hole state are actually dialed and are not skipped
|
// of the black hole state are actually dialed and are not skipped because of dial prioritisation
|
||||||
// because of dial prioritisation logic.
|
// logic.
|
||||||
type blackHoleDetector struct {
|
type blackHoleDetector struct {
|
||||||
udp, ipv6 *blackHoleFilter
|
udp, ipv6 *BlackHoleSuccessCounter
|
||||||
|
mt MetricsTracer
|
||||||
|
readOnly bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// FilterAddrs filters the peer's addresses removing black holed addresses
|
// FilterAddrs filters the peer's addresses removing black holed addresses
|
||||||
@@ -192,14 +195,16 @@ func (d *blackHoleDetector) FilterAddrs(addrs []ma.Multiaddr) (valid []ma.Multia
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
udpRes := blackHoleResultAllowed
|
udpRes := blackHoleStateAllowed
|
||||||
if d.udp != nil && hasUDP {
|
if d.udp != nil && hasUDP {
|
||||||
udpRes = d.udp.HandleRequest()
|
udpRes = d.getFilterState(d.udp)
|
||||||
|
d.trackMetrics(d.udp)
|
||||||
}
|
}
|
||||||
|
|
||||||
ipv6Res := blackHoleResultAllowed
|
ipv6Res := blackHoleStateAllowed
|
||||||
if d.ipv6 != nil && hasIPv6 {
|
if d.ipv6 != nil && hasIPv6 {
|
||||||
ipv6Res = d.ipv6.HandleRequest()
|
ipv6Res = d.getFilterState(d.ipv6)
|
||||||
|
d.trackMetrics(d.ipv6)
|
||||||
}
|
}
|
||||||
|
|
||||||
blackHoled = make([]ma.Multiaddr, 0, len(addrs))
|
blackHoled = make([]ma.Multiaddr, 0, len(addrs))
|
||||||
@@ -210,19 +215,19 @@ func (d *blackHoleDetector) FilterAddrs(addrs []ma.Multiaddr) (valid []ma.Multia
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
// allow all UDP addresses while probing irrespective of IPv6 black hole state
|
// allow all UDP addresses while probing irrespective of IPv6 black hole state
|
||||||
if udpRes == blackHoleResultProbing && isProtocolAddr(a, ma.P_UDP) {
|
if udpRes == blackHoleStateProbing && isProtocolAddr(a, ma.P_UDP) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
// allow all IPv6 addresses while probing irrespective of UDP black hole state
|
// allow all IPv6 addresses while probing irrespective of UDP black hole state
|
||||||
if ipv6Res == blackHoleResultProbing && isProtocolAddr(a, ma.P_IP6) {
|
if ipv6Res == blackHoleStateProbing && isProtocolAddr(a, ma.P_IP6) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if udpRes == blackHoleResultBlocked && isProtocolAddr(a, ma.P_UDP) {
|
if udpRes == blackHoleStateBlocked && isProtocolAddr(a, ma.P_UDP) {
|
||||||
blackHoled = append(blackHoled, a)
|
blackHoled = append(blackHoled, a)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if ipv6Res == blackHoleResultBlocked && isProtocolAddr(a, ma.P_IP6) {
|
if ipv6Res == blackHoleStateBlocked && isProtocolAddr(a, ma.P_IP6) {
|
||||||
blackHoled = append(blackHoled, a)
|
blackHoled = append(blackHoled, a)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -231,49 +236,36 @@ func (d *blackHoleDetector) FilterAddrs(addrs []ma.Multiaddr) (valid []ma.Multia
|
|||||||
), blackHoled
|
), blackHoled
|
||||||
}
|
}
|
||||||
|
|
||||||
// RecordResult updates the state of the relevant `blackHoleFilter`s for addr
|
// RecordResult updates the state of the relevant BlackHoleSuccessCounters for addr
|
||||||
func (d *blackHoleDetector) RecordResult(addr ma.Multiaddr, success bool) {
|
func (d *blackHoleDetector) RecordResult(addr ma.Multiaddr, success bool) {
|
||||||
if !manet.IsPublicAddr(addr) {
|
if d.readOnly || !manet.IsPublicAddr(addr) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if d.udp != nil && isProtocolAddr(addr, ma.P_UDP) {
|
if d.udp != nil && isProtocolAddr(addr, ma.P_UDP) {
|
||||||
d.udp.RecordResult(success)
|
d.udp.RecordResult(success)
|
||||||
|
d.trackMetrics(d.udp)
|
||||||
}
|
}
|
||||||
if d.ipv6 != nil && isProtocolAddr(addr, ma.P_IP6) {
|
if d.ipv6 != nil && isProtocolAddr(addr, ma.P_IP6) {
|
||||||
d.ipv6.RecordResult(success)
|
d.ipv6.RecordResult(success)
|
||||||
|
d.trackMetrics(d.ipv6)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// blackHoleConfig is the config used for black hole detection
|
func (d *blackHoleDetector) getFilterState(f *BlackHoleSuccessCounter) blackHoleState {
|
||||||
type blackHoleConfig struct {
|
if d.readOnly {
|
||||||
// Enabled enables black hole detection
|
if f.State() != blackHoleStateAllowed {
|
||||||
Enabled bool
|
return blackHoleStateBlocked
|
||||||
// N is the size of the sliding window used to evaluate black hole state
|
|
||||||
N int
|
|
||||||
// MinSuccesses is the minimum number of successes out of N required to not
|
|
||||||
// block requests
|
|
||||||
MinSuccesses int
|
|
||||||
}
|
|
||||||
|
|
||||||
func newBlackHoleDetector(udpConfig, ipv6Config blackHoleConfig, mt MetricsTracer) *blackHoleDetector {
|
|
||||||
d := &blackHoleDetector{}
|
|
||||||
|
|
||||||
if udpConfig.Enabled {
|
|
||||||
d.udp = &blackHoleFilter{
|
|
||||||
n: udpConfig.N,
|
|
||||||
minSuccesses: udpConfig.MinSuccesses,
|
|
||||||
name: "UDP",
|
|
||||||
metricsTracer: mt,
|
|
||||||
}
|
}
|
||||||
|
return blackHoleStateAllowed
|
||||||
}
|
}
|
||||||
|
return f.HandleRequest()
|
||||||
if ipv6Config.Enabled {
|
}
|
||||||
d.ipv6 = &blackHoleFilter{
|
|
||||||
n: ipv6Config.N,
|
func (d *blackHoleDetector) trackMetrics(f *BlackHoleSuccessCounter) {
|
||||||
minSuccesses: ipv6Config.MinSuccesses,
|
if d.readOnly || d.mt == nil {
|
||||||
name: "IPv6",
|
return
|
||||||
metricsTracer: mt,
|
}
|
||||||
}
|
// Track metrics only in non readOnly state
|
||||||
}
|
info := f.info()
|
||||||
return d
|
d.mt.UpdatedBlackHoleSuccessCounter(info.name, info.state, info.nextProbeAfter, info.successFraction)
|
||||||
}
|
}
|
||||||
|
@@ -8,58 +8,70 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestBlackHoleFilterReset(t *testing.T) {
|
func TestBlackHoleSuccessCounterReset(t *testing.T) {
|
||||||
n := 10
|
n := 10
|
||||||
bhf := &blackHoleFilter{n: n, minSuccesses: 2, name: "test"}
|
bhf := &BlackHoleSuccessCounter{N: n, MinSuccesses: 2, Name: "test"}
|
||||||
var i = 0
|
var i = 0
|
||||||
// calls up to n should be probing
|
// calls up to n should be probing
|
||||||
for i = 1; i <= n; i++ {
|
for i = 1; i <= n; i++ {
|
||||||
if bhf.HandleRequest() != blackHoleResultProbing {
|
if bhf.HandleRequest() != blackHoleStateProbing {
|
||||||
t.Fatalf("expected calls up to n to be probes")
|
t.Fatalf("expected calls up to n to be probes")
|
||||||
}
|
}
|
||||||
|
if bhf.State() != blackHoleStateProbing {
|
||||||
|
t.Fatalf("expected state to be probing got %s", bhf.State())
|
||||||
|
}
|
||||||
bhf.RecordResult(false)
|
bhf.RecordResult(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// after threshold calls every nth call should be a probe
|
// after threshold calls every nth call should be a probe
|
||||||
for i = n + 1; i < 42; i++ {
|
for i = n + 1; i < 42; i++ {
|
||||||
result := bhf.HandleRequest()
|
result := bhf.HandleRequest()
|
||||||
if (i%n == 0 && result != blackHoleResultProbing) || (i%n != 0 && result != blackHoleResultBlocked) {
|
if (i%n == 0 && result != blackHoleStateProbing) || (i%n != 0 && result != blackHoleStateBlocked) {
|
||||||
t.Fatalf("expected every nth dial to be a probe")
|
t.Fatalf("expected every nth dial to be a probe")
|
||||||
}
|
}
|
||||||
|
if bhf.State() != blackHoleStateBlocked {
|
||||||
|
t.Fatalf("expected state to be blocked, got %s", bhf.State())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bhf.RecordResult(true)
|
bhf.RecordResult(true)
|
||||||
// check if calls up to n are probes again
|
// check if calls up to n are probes again
|
||||||
for i = 0; i < n; i++ {
|
for i = 0; i < n; i++ {
|
||||||
if bhf.HandleRequest() != blackHoleResultProbing {
|
if bhf.HandleRequest() != blackHoleStateProbing {
|
||||||
t.Fatalf("expected black hole detector state to reset after success")
|
t.Fatalf("expected black hole detector state to reset after success")
|
||||||
}
|
}
|
||||||
|
if bhf.State() != blackHoleStateProbing {
|
||||||
|
t.Fatalf("expected state to be probing got %s", bhf.State())
|
||||||
|
}
|
||||||
bhf.RecordResult(false)
|
bhf.RecordResult(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// next call should be blocked
|
// next call should be blocked
|
||||||
if bhf.HandleRequest() != blackHoleResultBlocked {
|
if bhf.HandleRequest() != blackHoleStateBlocked {
|
||||||
t.Fatalf("expected dial to be blocked")
|
t.Fatalf("expected dial to be blocked")
|
||||||
|
if bhf.State() != blackHoleStateBlocked {
|
||||||
|
t.Fatalf("expected state to be blocked, got %s", bhf.State())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBlackHoleFilterSuccessFraction(t *testing.T) {
|
func TestBlackHoleSuccessCounterSuccessFraction(t *testing.T) {
|
||||||
n := 10
|
n := 10
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
minSuccesses, successes int
|
minSuccesses, successes int
|
||||||
result blackHoleResult
|
result blackHoleState
|
||||||
}{
|
}{
|
||||||
{minSuccesses: 5, successes: 5, result: blackHoleResultAllowed},
|
{minSuccesses: 5, successes: 5, result: blackHoleStateAllowed},
|
||||||
{minSuccesses: 3, successes: 3, result: blackHoleResultAllowed},
|
{minSuccesses: 3, successes: 3, result: blackHoleStateAllowed},
|
||||||
{minSuccesses: 5, successes: 4, result: blackHoleResultBlocked},
|
{minSuccesses: 5, successes: 4, result: blackHoleStateBlocked},
|
||||||
{minSuccesses: 5, successes: 7, result: blackHoleResultAllowed},
|
{minSuccesses: 5, successes: 7, result: blackHoleStateAllowed},
|
||||||
{minSuccesses: 3, successes: 1, result: blackHoleResultBlocked},
|
{minSuccesses: 3, successes: 1, result: blackHoleStateBlocked},
|
||||||
{minSuccesses: 0, successes: 0, result: blackHoleResultAllowed},
|
{minSuccesses: 0, successes: 0, result: blackHoleStateAllowed},
|
||||||
{minSuccesses: 10, successes: 10, result: blackHoleResultAllowed},
|
{minSuccesses: 10, successes: 10, result: blackHoleStateAllowed},
|
||||||
}
|
}
|
||||||
for i, tc := range tests {
|
for i, tc := range tests {
|
||||||
t.Run(fmt.Sprintf("case-%d", i), func(t *testing.T) {
|
t.Run(fmt.Sprintf("case-%d", i), func(t *testing.T) {
|
||||||
bhf := blackHoleFilter{n: n, minSuccesses: tc.minSuccesses}
|
bhf := BlackHoleSuccessCounter{N: n, MinSuccesses: tc.minSuccesses}
|
||||||
for i := 0; i < tc.successes; i++ {
|
for i := 0; i < tc.successes; i++ {
|
||||||
bhf.RecordResult(true)
|
bhf.RecordResult(true)
|
||||||
}
|
}
|
||||||
@@ -75,9 +87,9 @@ func TestBlackHoleFilterSuccessFraction(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestBlackHoleDetectorInApplicableAddress(t *testing.T) {
|
func TestBlackHoleDetectorInApplicableAddress(t *testing.T) {
|
||||||
udpConfig := blackHoleConfig{Enabled: true, N: 10, MinSuccesses: 5}
|
udpF := &BlackHoleSuccessCounter{N: 10, MinSuccesses: 5}
|
||||||
ipv6Config := blackHoleConfig{Enabled: true, N: 10, MinSuccesses: 5}
|
ipv6F := &BlackHoleSuccessCounter{N: 10, MinSuccesses: 5}
|
||||||
bhd := newBlackHoleDetector(udpConfig, ipv6Config, nil)
|
bhd := &blackHoleDetector{udp: udpF, ipv6: ipv6F}
|
||||||
addrs := []ma.Multiaddr{
|
addrs := []ma.Multiaddr{
|
||||||
ma.StringCast("/ip4/1.2.3.4/tcp/1234"),
|
ma.StringCast("/ip4/1.2.3.4/tcp/1234"),
|
||||||
ma.StringCast("/ip4/1.2.3.4/tcp/1233"),
|
ma.StringCast("/ip4/1.2.3.4/tcp/1233"),
|
||||||
@@ -94,8 +106,8 @@ func TestBlackHoleDetectorInApplicableAddress(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestBlackHoleDetectorUDPDisabled(t *testing.T) {
|
func TestBlackHoleDetectorUDPDisabled(t *testing.T) {
|
||||||
ipv6Config := blackHoleConfig{Enabled: true, N: 10, MinSuccesses: 5}
|
ipv6F := &BlackHoleSuccessCounter{N: 10, MinSuccesses: 5}
|
||||||
bhd := newBlackHoleDetector(blackHoleConfig{Enabled: false}, ipv6Config, nil)
|
bhd := &blackHoleDetector{ipv6: ipv6F}
|
||||||
publicAddr := ma.StringCast("/ip4/1.2.3.4/udp/1234/quic-v1")
|
publicAddr := ma.StringCast("/ip4/1.2.3.4/udp/1234/quic-v1")
|
||||||
privAddr := ma.StringCast("/ip4/192.168.1.5/udp/1234/quic-v1")
|
privAddr := ma.StringCast("/ip4/192.168.1.5/udp/1234/quic-v1")
|
||||||
for i := 0; i < 100; i++ {
|
for i := 0; i < 100; i++ {
|
||||||
@@ -110,8 +122,8 @@ func TestBlackHoleDetectorUDPDisabled(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestBlackHoleDetectorIPv6Disabled(t *testing.T) {
|
func TestBlackHoleDetectorIPv6Disabled(t *testing.T) {
|
||||||
udpConfig := blackHoleConfig{Enabled: true, N: 10, MinSuccesses: 5}
|
udpF := &BlackHoleSuccessCounter{N: 10, MinSuccesses: 5}
|
||||||
bhd := newBlackHoleDetector(udpConfig, blackHoleConfig{Enabled: false}, nil)
|
bhd := &blackHoleDetector{udp: udpF}
|
||||||
publicAddr := ma.StringCast("/ip6/2001::1/tcp/1234")
|
publicAddr := ma.StringCast("/ip6/2001::1/tcp/1234")
|
||||||
privAddr := ma.StringCast("/ip6/::1/tcp/1234")
|
privAddr := ma.StringCast("/ip6/::1/tcp/1234")
|
||||||
for i := 0; i < 100; i++ {
|
for i := 0; i < 100; i++ {
|
||||||
@@ -128,8 +140,8 @@ func TestBlackHoleDetectorIPv6Disabled(t *testing.T) {
|
|||||||
|
|
||||||
func TestBlackHoleDetectorProbes(t *testing.T) {
|
func TestBlackHoleDetectorProbes(t *testing.T) {
|
||||||
bhd := &blackHoleDetector{
|
bhd := &blackHoleDetector{
|
||||||
udp: &blackHoleFilter{n: 2, minSuccesses: 1, name: "udp"},
|
udp: &BlackHoleSuccessCounter{N: 2, MinSuccesses: 1, Name: "udp"},
|
||||||
ipv6: &blackHoleFilter{n: 3, minSuccesses: 1, name: "ipv6"},
|
ipv6: &BlackHoleSuccessCounter{N: 3, MinSuccesses: 1, Name: "ipv6"},
|
||||||
}
|
}
|
||||||
udp6Addr := ma.StringCast("/ip6/2001::1/udp/1234/quic-v1")
|
udp6Addr := ma.StringCast("/ip6/2001::1/udp/1234/quic-v1")
|
||||||
addrs := []ma.Multiaddr{udp6Addr}
|
addrs := []ma.Multiaddr{udp6Addr}
|
||||||
@@ -163,8 +175,8 @@ func TestBlackHoleDetectorAddrFiltering(t *testing.T) {
|
|||||||
|
|
||||||
makeBHD := func(udpBlocked, ipv6Blocked bool) *blackHoleDetector {
|
makeBHD := func(udpBlocked, ipv6Blocked bool) *blackHoleDetector {
|
||||||
bhd := &blackHoleDetector{
|
bhd := &blackHoleDetector{
|
||||||
udp: &blackHoleFilter{n: 100, minSuccesses: 10, name: "udp"},
|
udp: &BlackHoleSuccessCounter{N: 100, MinSuccesses: 10, Name: "udp"},
|
||||||
ipv6: &blackHoleFilter{n: 100, minSuccesses: 10, name: "ipv6"},
|
ipv6: &BlackHoleSuccessCounter{N: 100, MinSuccesses: 10, Name: "ipv6"},
|
||||||
}
|
}
|
||||||
for i := 0; i < 100; i++ {
|
for i := 0; i < 100; i++ {
|
||||||
bhd.RecordResult(udp4Pub, !udpBlocked)
|
bhd.RecordResult(udp4Pub, !udpBlocked)
|
||||||
@@ -199,3 +211,35 @@ func TestBlackHoleDetectorAddrFiltering(t *testing.T) {
|
|||||||
require.ElementsMatch(t, bothBlockedOutput, gotAddrs)
|
require.ElementsMatch(t, bothBlockedOutput, gotAddrs)
|
||||||
require.ElementsMatch(t, bothPublicAddrs, gotRemovedAddrs)
|
require.ElementsMatch(t, bothPublicAddrs, gotRemovedAddrs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBlackHoleDetectorReadOnlyMode(t *testing.T) {
|
||||||
|
udpF := &BlackHoleSuccessCounter{N: 10, MinSuccesses: 5}
|
||||||
|
ipv6F := &BlackHoleSuccessCounter{N: 10, MinSuccesses: 5}
|
||||||
|
bhd := &blackHoleDetector{udp: udpF, ipv6: ipv6F, readOnly: true}
|
||||||
|
publicAddr := ma.StringCast("/ip4/1.2.3.4/udp/1234/quic-v1")
|
||||||
|
privAddr := ma.StringCast("/ip6/::1/tcp/1234")
|
||||||
|
for i := 0; i < 100; i++ {
|
||||||
|
bhd.RecordResult(publicAddr, true)
|
||||||
|
}
|
||||||
|
allAddr := []ma.Multiaddr{privAddr, publicAddr}
|
||||||
|
// public addr filtered because state is probing
|
||||||
|
wantAddrs := []ma.Multiaddr{privAddr}
|
||||||
|
wantRemovedAddrs := []ma.Multiaddr{publicAddr}
|
||||||
|
|
||||||
|
gotAddrs, gotRemovedAddrs := bhd.FilterAddrs(allAddr)
|
||||||
|
require.ElementsMatch(t, wantAddrs, gotAddrs)
|
||||||
|
require.ElementsMatch(t, wantRemovedAddrs, gotRemovedAddrs)
|
||||||
|
|
||||||
|
// a non readonly shared state black hole detector
|
||||||
|
nbhd := &blackHoleDetector{udp: bhd.udp, ipv6: bhd.ipv6, readOnly: false}
|
||||||
|
for i := 0; i < 100; i++ {
|
||||||
|
nbhd.RecordResult(publicAddr, true)
|
||||||
|
}
|
||||||
|
// no addresses filtered because state is allowed
|
||||||
|
wantAddrs = []ma.Multiaddr{privAddr, publicAddr}
|
||||||
|
wantRemovedAddrs = []ma.Multiaddr{}
|
||||||
|
|
||||||
|
gotAddrs, gotRemovedAddrs = bhd.FilterAddrs(allAddr)
|
||||||
|
require.ElementsMatch(t, wantAddrs, gotAddrs)
|
||||||
|
require.ElementsMatch(t, wantRemovedAddrs, gotRemovedAddrs)
|
||||||
|
}
|
||||||
|
@@ -112,22 +112,33 @@ func WithDialRanker(d network.DialRanker) Option {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithUDPBlackHoleConfig configures swarm to use c as the config for UDP black hole detection
|
// WithUDPBlackHoleSuccessCounter configures swarm to use the provided config for UDP black hole detection
|
||||||
// n is the size of the sliding window used to evaluate black hole state
|
// n is the size of the sliding window used to evaluate black hole state
|
||||||
// min is the minimum number of successes out of n required to not block requests
|
// min is the minimum number of successes out of n required to not block requests
|
||||||
func WithUDPBlackHoleConfig(enabled bool, n, min int) Option {
|
func WithUDPBlackHoleSuccessCounter(f *BlackHoleSuccessCounter) Option {
|
||||||
return func(s *Swarm) error {
|
return func(s *Swarm) error {
|
||||||
s.udpBlackHoleConfig = blackHoleConfig{Enabled: enabled, N: n, MinSuccesses: min}
|
s.udpBHF = f
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithIPv6BlackHoleConfig configures swarm to use c as the config for IPv6 black hole detection
|
// WithIPv6BlackHoleSuccessCounter configures swarm to use the provided config for IPv6 black hole detection
|
||||||
// n is the size of the sliding window used to evaluate black hole state
|
// n is the size of the sliding window used to evaluate black hole state
|
||||||
// min is the minimum number of successes out of n required to not block requests
|
// min is the minimum number of successes out of n required to not block requests
|
||||||
func WithIPv6BlackHoleConfig(enabled bool, n, min int) Option {
|
func WithIPv6BlackHoleSuccessCounter(f *BlackHoleSuccessCounter) Option {
|
||||||
return func(s *Swarm) error {
|
return func(s *Swarm) error {
|
||||||
s.ipv6BlackHoleConfig = blackHoleConfig{Enabled: enabled, N: n, MinSuccesses: min}
|
s.ipv6BHF = f
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithReadOnlyBlackHoleDetector configures the swarm to use the black hole detector in
|
||||||
|
// read only mode. In Read Only mode dial requests are refused in unknown state and
|
||||||
|
// no updates to the detector state are made. This is useful for services like AutoNAT that
|
||||||
|
// care about accurately providing reachability info.
|
||||||
|
func WithReadOnlyBlackHoleDetector() Option {
|
||||||
|
return func(s *Swarm) error {
|
||||||
|
s.readOnlyBHD = true
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -203,10 +214,11 @@ type Swarm struct {
|
|||||||
|
|
||||||
dialRanker network.DialRanker
|
dialRanker network.DialRanker
|
||||||
|
|
||||||
udpBlackHoleConfig blackHoleConfig
|
|
||||||
ipv6BlackHoleConfig blackHoleConfig
|
|
||||||
bhd *blackHoleDetector
|
|
||||||
connectednessEventEmitter *connectednessEventEmitter
|
connectednessEventEmitter *connectednessEventEmitter
|
||||||
|
udpBHF *BlackHoleSuccessCounter
|
||||||
|
ipv6BHF *BlackHoleSuccessCounter
|
||||||
|
bhd *blackHoleDetector
|
||||||
|
readOnlyBHD bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSwarm constructs a Swarm.
|
// NewSwarm constructs a Swarm.
|
||||||
@@ -230,8 +242,8 @@ func NewSwarm(local peer.ID, peers peerstore.Peerstore, eventBus event.Bus, opts
|
|||||||
// A black hole is a binary property. On a network if UDP dials are blocked or there is
|
// A black hole is a binary property. On a network if UDP dials are blocked or there is
|
||||||
// no IPv6 connectivity, all dials will fail. So a low success rate of 5 out 100 dials
|
// no IPv6 connectivity, all dials will fail. So a low success rate of 5 out 100 dials
|
||||||
// is good enough.
|
// is good enough.
|
||||||
udpBlackHoleConfig: blackHoleConfig{Enabled: true, N: 100, MinSuccesses: 5},
|
udpBHF: &BlackHoleSuccessCounter{N: 100, MinSuccesses: 5, Name: "UDP"},
|
||||||
ipv6BlackHoleConfig: blackHoleConfig{Enabled: true, N: 100, MinSuccesses: 5},
|
ipv6BHF: &BlackHoleSuccessCounter{N: 100, MinSuccesses: 5, Name: "IPv6"},
|
||||||
}
|
}
|
||||||
|
|
||||||
s.conns.m = make(map[peer.ID][]*Conn)
|
s.conns.m = make(map[peer.ID][]*Conn)
|
||||||
@@ -255,7 +267,12 @@ func NewSwarm(local peer.ID, peers peerstore.Peerstore, eventBus event.Bus, opts
|
|||||||
s.limiter = newDialLimiter(s.dialAddr)
|
s.limiter = newDialLimiter(s.dialAddr)
|
||||||
s.backf.init(s.ctx)
|
s.backf.init(s.ctx)
|
||||||
|
|
||||||
s.bhd = newBlackHoleDetector(s.udpBlackHoleConfig, s.ipv6BlackHoleConfig, s.metricsTracer)
|
s.bhd = &blackHoleDetector{
|
||||||
|
udp: s.udpBHF,
|
||||||
|
ipv6: s.ipv6BHF,
|
||||||
|
mt: s.metricsTracer,
|
||||||
|
readOnly: s.readOnlyBHD,
|
||||||
|
}
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -416,6 +416,11 @@ func (s *Swarm) dialNextAddr(ctx context.Context, p peer.ID, addr ma.Multiaddr,
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Swarm) CanDial(p peer.ID, addr ma.Multiaddr) bool {
|
||||||
|
dialable, _ := s.filterKnownUndialables(p, []ma.Multiaddr{addr})
|
||||||
|
return len(dialable) > 0
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Swarm) nonProxyAddr(addr ma.Multiaddr) bool {
|
func (s *Swarm) nonProxyAddr(addr ma.Multiaddr) bool {
|
||||||
t := s.TransportForDialing(addr)
|
t := s.TransportForDialing(addr)
|
||||||
return !t.Proxy()
|
return !t.Proxy()
|
||||||
|
@@ -364,7 +364,7 @@ func TestBlackHoledAddrBlocked(t *testing.T) {
|
|||||||
defer s.Close()
|
defer s.Close()
|
||||||
|
|
||||||
n := 3
|
n := 3
|
||||||
s.bhd.ipv6 = &blackHoleFilter{n: n, minSuccesses: 1, name: "IPv6"}
|
s.bhd.ipv6 = &BlackHoleSuccessCounter{N: n, MinSuccesses: 1, Name: "IPv6"}
|
||||||
|
|
||||||
// All dials to this addr will fail.
|
// All dials to this addr will fail.
|
||||||
// manet.IsPublic is aggressive for IPv6 addresses. Use a NAT64 address.
|
// manet.IsPublic is aggressive for IPv6 addresses. Use a NAT64 address.
|
||||||
|
@@ -85,7 +85,7 @@ var (
|
|||||||
Buckets: []float64{0.001, 0.01, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 0.75, 1, 2},
|
Buckets: []float64{0.001, 0.01, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 0.75, 1, 2},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
blackHoleFilterState = prometheus.NewGaugeVec(
|
blackHoleSuccessCounterState = prometheus.NewGaugeVec(
|
||||||
prometheus.GaugeOpts{
|
prometheus.GaugeOpts{
|
||||||
Namespace: metricNamespace,
|
Namespace: metricNamespace,
|
||||||
Name: "black_hole_filter_state",
|
Name: "black_hole_filter_state",
|
||||||
@@ -93,7 +93,7 @@ var (
|
|||||||
},
|
},
|
||||||
[]string{"name"},
|
[]string{"name"},
|
||||||
)
|
)
|
||||||
blackHoleFilterSuccessFraction = prometheus.NewGaugeVec(
|
blackHoleSuccessCounterSuccessFraction = prometheus.NewGaugeVec(
|
||||||
prometheus.GaugeOpts{
|
prometheus.GaugeOpts{
|
||||||
Namespace: metricNamespace,
|
Namespace: metricNamespace,
|
||||||
Name: "black_hole_filter_success_fraction",
|
Name: "black_hole_filter_success_fraction",
|
||||||
@@ -101,7 +101,7 @@ var (
|
|||||||
},
|
},
|
||||||
[]string{"name"},
|
[]string{"name"},
|
||||||
)
|
)
|
||||||
blackHoleFilterNextRequestAllowedAfter = prometheus.NewGaugeVec(
|
blackHoleSuccessCounterNextRequestAllowedAfter = prometheus.NewGaugeVec(
|
||||||
prometheus.GaugeOpts{
|
prometheus.GaugeOpts{
|
||||||
Namespace: metricNamespace,
|
Namespace: metricNamespace,
|
||||||
Name: "black_hole_filter_next_request_allowed_after",
|
Name: "black_hole_filter_next_request_allowed_after",
|
||||||
@@ -118,9 +118,9 @@ var (
|
|||||||
connHandshakeLatency,
|
connHandshakeLatency,
|
||||||
dialsPerPeer,
|
dialsPerPeer,
|
||||||
dialRankingDelay,
|
dialRankingDelay,
|
||||||
blackHoleFilterSuccessFraction,
|
blackHoleSuccessCounterSuccessFraction,
|
||||||
blackHoleFilterState,
|
blackHoleSuccessCounterState,
|
||||||
blackHoleFilterNextRequestAllowedAfter,
|
blackHoleSuccessCounterNextRequestAllowedAfter,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -131,7 +131,7 @@ type MetricsTracer interface {
|
|||||||
FailedDialing(ma.Multiaddr, error, error)
|
FailedDialing(ma.Multiaddr, error, error)
|
||||||
DialCompleted(success bool, totalDials int)
|
DialCompleted(success bool, totalDials int)
|
||||||
DialRankingDelay(d time.Duration)
|
DialRankingDelay(d time.Duration)
|
||||||
UpdatedBlackHoleFilterState(name string, state blackHoleState, nextProbeAfter int, successFraction float64)
|
UpdatedBlackHoleSuccessCounter(name string, state blackHoleState, nextProbeAfter int, successFraction float64)
|
||||||
}
|
}
|
||||||
|
|
||||||
type metricsTracer struct{}
|
type metricsTracer struct{}
|
||||||
@@ -274,14 +274,14 @@ func (m *metricsTracer) DialRankingDelay(d time.Duration) {
|
|||||||
dialRankingDelay.Observe(d.Seconds())
|
dialRankingDelay.Observe(d.Seconds())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *metricsTracer) UpdatedBlackHoleFilterState(name string, state blackHoleState,
|
func (m *metricsTracer) UpdatedBlackHoleSuccessCounter(name string, state blackHoleState,
|
||||||
nextProbeAfter int, successFraction float64) {
|
nextProbeAfter int, successFraction float64) {
|
||||||
tags := metricshelper.GetStringSlice()
|
tags := metricshelper.GetStringSlice()
|
||||||
defer metricshelper.PutStringSlice(tags)
|
defer metricshelper.PutStringSlice(tags)
|
||||||
|
|
||||||
*tags = append(*tags, name)
|
*tags = append(*tags, name)
|
||||||
|
|
||||||
blackHoleFilterState.WithLabelValues(*tags...).Set(float64(state))
|
blackHoleSuccessCounterState.WithLabelValues(*tags...).Set(float64(state))
|
||||||
blackHoleFilterSuccessFraction.WithLabelValues(*tags...).Set(successFraction)
|
blackHoleSuccessCounterSuccessFraction.WithLabelValues(*tags...).Set(successFraction)
|
||||||
blackHoleFilterNextRequestAllowedAfter.WithLabelValues(*tags...).Set(float64(nextProbeAfter))
|
blackHoleSuccessCounterNextRequestAllowedAfter.WithLabelValues(*tags...).Set(float64(nextProbeAfter))
|
||||||
}
|
}
|
||||||
|
@@ -94,8 +94,8 @@ func TestMetricsNoAllocNoCover(t *testing.T) {
|
|||||||
"FailedDialing": func() { mt.FailedDialing(randItem(addrs), randItem(errors), randItem(errors)) },
|
"FailedDialing": func() { mt.FailedDialing(randItem(addrs), randItem(errors), randItem(errors)) },
|
||||||
"DialCompleted": func() { mt.DialCompleted(mrand.Intn(2) == 1, mrand.Intn(10)) },
|
"DialCompleted": func() { mt.DialCompleted(mrand.Intn(2) == 1, mrand.Intn(10)) },
|
||||||
"DialRankingDelay": func() { mt.DialRankingDelay(time.Duration(mrand.Intn(1e10))) },
|
"DialRankingDelay": func() { mt.DialRankingDelay(time.Duration(mrand.Intn(1e10))) },
|
||||||
"UpdatedBlackHoleFilterState": func() {
|
"UpdatedBlackHoleSuccessCounter": func() {
|
||||||
mt.UpdatedBlackHoleFilterState(
|
mt.UpdatedBlackHoleSuccessCounter(
|
||||||
randItem(bhfNames),
|
randItem(bhfNames),
|
||||||
randItem(bhfState),
|
randItem(bhfState),
|
||||||
mrand.Intn(100),
|
mrand.Intn(100),
|
||||||
|
@@ -53,63 +53,63 @@ func (rc realclock) Now() time.Time {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Option is an option that can be passed when constructing a test swarm.
|
// Option is an option that can be passed when constructing a test swarm.
|
||||||
type Option func(*testing.T, *config)
|
type Option func(testing.TB, *config)
|
||||||
|
|
||||||
// WithClock sets the clock to use for this swarm
|
// WithClock sets the clock to use for this swarm
|
||||||
func WithClock(clock clock) Option {
|
func WithClock(clock clock) Option {
|
||||||
return func(_ *testing.T, c *config) {
|
return func(_ testing.TB, c *config) {
|
||||||
c.clock = clock
|
c.clock = clock
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func WithSwarmOpts(swarmOpts ...swarm.Option) Option {
|
func WithSwarmOpts(swarmOpts ...swarm.Option) Option {
|
||||||
return func(_ *testing.T, c *config) {
|
return func(_ testing.TB, c *config) {
|
||||||
c.swarmOpts = swarmOpts
|
c.swarmOpts = swarmOpts
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// OptDisableReuseport disables reuseport in this test swarm.
|
// OptDisableReuseport disables reuseport in this test swarm.
|
||||||
var OptDisableReuseport Option = func(_ *testing.T, c *config) {
|
var OptDisableReuseport Option = func(_ testing.TB, c *config) {
|
||||||
c.disableReuseport = true
|
c.disableReuseport = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// OptDialOnly prevents the test swarm from listening.
|
// OptDialOnly prevents the test swarm from listening.
|
||||||
var OptDialOnly Option = func(_ *testing.T, c *config) {
|
var OptDialOnly Option = func(_ testing.TB, c *config) {
|
||||||
c.dialOnly = true
|
c.dialOnly = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// OptDisableTCP disables TCP.
|
// OptDisableTCP disables TCP.
|
||||||
var OptDisableTCP Option = func(_ *testing.T, c *config) {
|
var OptDisableTCP Option = func(_ testing.TB, c *config) {
|
||||||
c.disableTCP = true
|
c.disableTCP = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// OptDisableQUIC disables QUIC.
|
// OptDisableQUIC disables QUIC.
|
||||||
var OptDisableQUIC Option = func(_ *testing.T, c *config) {
|
var OptDisableQUIC Option = func(_ testing.TB, c *config) {
|
||||||
c.disableQUIC = true
|
c.disableQUIC = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// OptConnGater configures the given connection gater on the test
|
// OptConnGater configures the given connection gater on the test
|
||||||
func OptConnGater(cg connmgr.ConnectionGater) Option {
|
func OptConnGater(cg connmgr.ConnectionGater) Option {
|
||||||
return func(_ *testing.T, c *config) {
|
return func(_ testing.TB, c *config) {
|
||||||
c.connectionGater = cg
|
c.connectionGater = cg
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// OptPeerPrivateKey configures the peer private key which is then used to derive the public key and peer ID.
|
// OptPeerPrivateKey configures the peer private key which is then used to derive the public key and peer ID.
|
||||||
func OptPeerPrivateKey(sk crypto.PrivKey) Option {
|
func OptPeerPrivateKey(sk crypto.PrivKey) Option {
|
||||||
return func(_ *testing.T, c *config) {
|
return func(_ testing.TB, c *config) {
|
||||||
c.sk = sk
|
c.sk = sk
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func EventBus(b event.Bus) Option {
|
func EventBus(b event.Bus) Option {
|
||||||
return func(_ *testing.T, c *config) {
|
return func(_ testing.TB, c *config) {
|
||||||
c.eventBus = b
|
c.eventBus = b
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GenUpgrader creates a new connection upgrader for use with this swarm.
|
// GenUpgrader creates a new connection upgrader for use with this swarm.
|
||||||
func GenUpgrader(t *testing.T, n *swarm.Swarm, connGater connmgr.ConnectionGater, opts ...tptu.Option) transport.Upgrader {
|
func GenUpgrader(t testing.TB, n *swarm.Swarm, connGater connmgr.ConnectionGater, opts ...tptu.Option) transport.Upgrader {
|
||||||
id := n.LocalPeer()
|
id := n.LocalPeer()
|
||||||
pk := n.Peerstore().PrivKey(id)
|
pk := n.Peerstore().PrivKey(id)
|
||||||
st := insecure.NewWithIdentity(insecure.ID, id, pk)
|
st := insecure.NewWithIdentity(insecure.ID, id, pk)
|
||||||
@@ -120,7 +120,7 @@ func GenUpgrader(t *testing.T, n *swarm.Swarm, connGater connmgr.ConnectionGater
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GenSwarm generates a new test swarm.
|
// GenSwarm generates a new test swarm.
|
||||||
func GenSwarm(t *testing.T, opts ...Option) *swarm.Swarm {
|
func GenSwarm(t testing.TB, opts ...Option) *swarm.Swarm {
|
||||||
var cfg config
|
var cfg config
|
||||||
cfg.clock = realclock{}
|
cfg.clock = realclock{}
|
||||||
for _, o := range opts {
|
for _, o := range opts {
|
||||||
|
236
p2p/protocol/autonatv2/autonat.go
Normal file
236
p2p/protocol/autonatv2/autonat.go
Normal file
@@ -0,0 +1,236 @@
|
|||||||
|
package autonatv2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
logging "github.com/ipfs/go-log/v2"
|
||||||
|
"github.com/libp2p/go-libp2p/core/event"
|
||||||
|
"github.com/libp2p/go-libp2p/core/host"
|
||||||
|
"github.com/libp2p/go-libp2p/core/network"
|
||||||
|
"github.com/libp2p/go-libp2p/core/peer"
|
||||||
|
"github.com/libp2p/go-libp2p/p2p/protocol/autonatv2/pb"
|
||||||
|
ma "github.com/multiformats/go-multiaddr"
|
||||||
|
manet "github.com/multiformats/go-multiaddr/net"
|
||||||
|
"golang.org/x/exp/rand"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:generate protoc --go_out=. --go_opt=Mpb/autonatv2.proto=./pb pb/autonatv2.proto
|
||||||
|
|
||||||
|
const (
|
||||||
|
ServiceName = "libp2p.autonatv2"
|
||||||
|
DialBackProtocol = "/libp2p/autonat/2/dial-back"
|
||||||
|
DialProtocol = "/libp2p/autonat/2/dial-request"
|
||||||
|
|
||||||
|
maxMsgSize = 8192
|
||||||
|
streamTimeout = time.Minute
|
||||||
|
dialBackStreamTimeout = 5 * time.Second
|
||||||
|
dialBackDialTimeout = 30 * time.Second
|
||||||
|
dialBackMaxMsgSize = 1024
|
||||||
|
minHandshakeSizeBytes = 30_000 // for amplification attack prevention
|
||||||
|
maxHandshakeSizeBytes = 100_000
|
||||||
|
// maxPeerAddresses is the number of addresses in a dial request the server
|
||||||
|
// will inspect, rest are ignored.
|
||||||
|
maxPeerAddresses = 50
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrNoValidPeers = errors.New("no valid peers for autonat v2")
|
||||||
|
ErrDialRefused = errors.New("dial refused")
|
||||||
|
|
||||||
|
log = logging.Logger("autonatv2")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Request is the request to verify reachability of a single address
|
||||||
|
type Request struct {
|
||||||
|
// Addr is the multiaddr to verify
|
||||||
|
Addr ma.Multiaddr
|
||||||
|
// SendDialData indicates whether to send dial data if the server requests it for Addr
|
||||||
|
SendDialData bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Result is the result of the CheckReachability call
|
||||||
|
type Result struct {
|
||||||
|
// Addr is the dialed address
|
||||||
|
Addr ma.Multiaddr
|
||||||
|
// Reachability of the dialed address
|
||||||
|
Reachability network.Reachability
|
||||||
|
// Status is the outcome of the dialback
|
||||||
|
Status pb.DialStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
// AutoNAT implements the AutoNAT v2 client and server.
|
||||||
|
// Users can check reachability for their addresses using the CheckReachability method.
|
||||||
|
// The server provides amplification attack prevention and rate limiting.
|
||||||
|
type AutoNAT struct {
|
||||||
|
host host.Host
|
||||||
|
|
||||||
|
// for cleanly closing
|
||||||
|
ctx context.Context
|
||||||
|
cancel context.CancelFunc
|
||||||
|
wg sync.WaitGroup
|
||||||
|
|
||||||
|
srv *server
|
||||||
|
cli *client
|
||||||
|
|
||||||
|
mx sync.Mutex
|
||||||
|
peers *peersMap
|
||||||
|
// allowPrivateAddrs enables using private and localhost addresses for reachability checks.
|
||||||
|
// This is only useful for testing.
|
||||||
|
allowPrivateAddrs bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a new AutoNAT instance.
|
||||||
|
// host and dialerHost should have the same dialing capabilities. In case the host doesn't support
|
||||||
|
// a transport, dial back requests for address for that transport will be ignored.
|
||||||
|
func New(host host.Host, dialerHost host.Host, opts ...AutoNATOption) (*AutoNAT, error) {
|
||||||
|
s := defaultSettings()
|
||||||
|
for _, o := range opts {
|
||||||
|
if err := o(s); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to apply option: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
an := &AutoNAT{
|
||||||
|
host: host,
|
||||||
|
ctx: ctx,
|
||||||
|
cancel: cancel,
|
||||||
|
srv: newServer(host, dialerHost, s),
|
||||||
|
cli: newClient(host),
|
||||||
|
allowPrivateAddrs: s.allowPrivateAddrs,
|
||||||
|
peers: newPeersMap(),
|
||||||
|
}
|
||||||
|
return an, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (an *AutoNAT) background(sub event.Subscription) {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-an.ctx.Done():
|
||||||
|
sub.Close()
|
||||||
|
an.wg.Done()
|
||||||
|
return
|
||||||
|
case e := <-sub.Out():
|
||||||
|
switch evt := e.(type) {
|
||||||
|
case event.EvtPeerProtocolsUpdated:
|
||||||
|
an.updatePeer(evt.Peer)
|
||||||
|
case event.EvtPeerConnectednessChanged:
|
||||||
|
an.updatePeer(evt.Peer)
|
||||||
|
case event.EvtPeerIdentificationCompleted:
|
||||||
|
an.updatePeer(evt.Peer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (an *AutoNAT) Start() error {
|
||||||
|
// Listen on event.EvtPeerProtocolsUpdated, event.EvtPeerConnectednessChanged
|
||||||
|
// event.EvtPeerIdentificationCompleted to maintain our set of autonat supporting peers.
|
||||||
|
sub, err := an.host.EventBus().Subscribe([]interface{}{
|
||||||
|
new(event.EvtPeerProtocolsUpdated),
|
||||||
|
new(event.EvtPeerConnectednessChanged),
|
||||||
|
new(event.EvtPeerIdentificationCompleted),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("event subscription failed: %w", err)
|
||||||
|
}
|
||||||
|
an.cli.Start()
|
||||||
|
an.srv.Start()
|
||||||
|
|
||||||
|
an.wg.Add(1)
|
||||||
|
go an.background(sub)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (an *AutoNAT) Close() {
|
||||||
|
an.cancel()
|
||||||
|
an.wg.Wait()
|
||||||
|
an.srv.Close()
|
||||||
|
an.cli.Close()
|
||||||
|
an.peers = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.allowPrivateAddrs {
|
||||||
|
for _, r := range reqs {
|
||||||
|
if !manet.IsPublicAddr(r.Addr) {
|
||||||
|
return Result{}, fmt.Errorf("private address cannot be verified by autonatv2: %s", r.Addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
an.mx.Lock()
|
||||||
|
p := an.peers.GetRand()
|
||||||
|
an.mx.Unlock()
|
||||||
|
if p == "" {
|
||||||
|
return Result{}, ErrNoValidPeers
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := an.cli.GetReachability(ctx, p, reqs)
|
||||||
|
if err != nil {
|
||||||
|
log.Debugf("reachability check with %s failed, err: %s", p, err)
|
||||||
|
return Result{}, fmt.Errorf("reachability check with %s failed: %w", p, err)
|
||||||
|
}
|
||||||
|
log.Debugf("reachability check with %s successful", p)
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (an *AutoNAT) updatePeer(p peer.ID) {
|
||||||
|
an.mx.Lock()
|
||||||
|
defer an.mx.Unlock()
|
||||||
|
|
||||||
|
// There are no ordering gurantees between identify and swarm events. Check peerstore
|
||||||
|
// and swarm for the current state
|
||||||
|
protos, err := an.host.Peerstore().SupportsProtocols(p, DialProtocol)
|
||||||
|
connectedness := an.host.Network().Connectedness(p)
|
||||||
|
if err == nil && slices.Contains(protos, DialProtocol) && connectedness == network.Connected {
|
||||||
|
an.peers.Put(p)
|
||||||
|
} else {
|
||||||
|
an.peers.Delete(p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// peersMap provides random access to a set of peers. This is useful when the map iteration order is
|
||||||
|
// not sufficiently random.
|
||||||
|
type peersMap struct {
|
||||||
|
peerIdx map[peer.ID]int
|
||||||
|
peers []peer.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
func newPeersMap() *peersMap {
|
||||||
|
return &peersMap{
|
||||||
|
peerIdx: make(map[peer.ID]int),
|
||||||
|
peers: make([]peer.ID, 0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *peersMap) GetRand() peer.ID {
|
||||||
|
if len(p.peers) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return p.peers[rand.Intn(len(p.peers))]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *peersMap) Put(pid peer.ID) {
|
||||||
|
if _, ok := p.peerIdx[pid]; ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.peers = append(p.peers, pid)
|
||||||
|
p.peerIdx[pid] = len(p.peers) - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *peersMap) Delete(pid peer.ID) {
|
||||||
|
idx, ok := p.peerIdx[pid]
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.peers[idx] = p.peers[len(p.peers)-1]
|
||||||
|
p.peerIdx[p.peers[idx]] = idx
|
||||||
|
p.peers = p.peers[:len(p.peers)-1]
|
||||||
|
delete(p.peerIdx, pid)
|
||||||
|
}
|
661
p2p/protocol/autonatv2/autonat_test.go
Normal file
661
p2p/protocol/autonatv2/autonat_test.go
Normal file
@@ -0,0 +1,661 @@
|
|||||||
|
package autonatv2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"sync/atomic"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/libp2p/go-libp2p/core/host"
|
||||||
|
"github.com/libp2p/go-libp2p/core/network"
|
||||||
|
"github.com/libp2p/go-libp2p/core/peer"
|
||||||
|
"github.com/libp2p/go-libp2p/core/peerstore"
|
||||||
|
bhost "github.com/libp2p/go-libp2p/p2p/host/blank"
|
||||||
|
"github.com/libp2p/go-libp2p/p2p/host/eventbus"
|
||||||
|
"github.com/libp2p/go-libp2p/p2p/net/swarm"
|
||||||
|
swarmt "github.com/libp2p/go-libp2p/p2p/net/swarm/testing"
|
||||||
|
"github.com/libp2p/go-libp2p/p2p/protocol/autonatv2/pb"
|
||||||
|
|
||||||
|
"github.com/libp2p/go-msgio/pbio"
|
||||||
|
ma "github.com/multiformats/go-multiaddr"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newAutoNAT(t testing.TB, dialer host.Host, opts ...AutoNATOption) *AutoNAT {
|
||||||
|
t.Helper()
|
||||||
|
b := eventbus.NewBus()
|
||||||
|
h := bhost.NewBlankHost(
|
||||||
|
swarmt.GenSwarm(t, swarmt.EventBus(b)), bhost.WithEventBus(b))
|
||||||
|
if dialer == nil {
|
||||||
|
dialer = bhost.NewBlankHost(
|
||||||
|
swarmt.GenSwarm(t,
|
||||||
|
swarmt.WithSwarmOpts(
|
||||||
|
swarm.WithUDPBlackHoleSuccessCounter(nil),
|
||||||
|
swarm.WithIPv6BlackHoleSuccessCounter(nil))))
|
||||||
|
}
|
||||||
|
an, err := New(h, dialer, opts...)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
an.Start()
|
||||||
|
t.Cleanup(an.Close)
|
||||||
|
return an
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseAddrs(t *testing.T, msg *pb.Message) []ma.Multiaddr {
|
||||||
|
t.Helper()
|
||||||
|
req := msg.GetDialRequest()
|
||||||
|
addrs := make([]ma.Multiaddr, 0)
|
||||||
|
for _, ab := range req.Addrs {
|
||||||
|
a, err := ma.NewMultiaddrBytes(ab)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("invalid addr bytes", ab)
|
||||||
|
}
|
||||||
|
addrs = append(addrs, a)
|
||||||
|
}
|
||||||
|
return addrs
|
||||||
|
}
|
||||||
|
|
||||||
|
// idAndConnect identifies b to a and connects them
|
||||||
|
func idAndConnect(t testing.TB, a, b host.Host) {
|
||||||
|
a.Peerstore().AddAddrs(b.ID(), b.Addrs(), peerstore.PermanentAddrTTL)
|
||||||
|
a.Peerstore().AddProtocols(b.ID(), DialProtocol)
|
||||||
|
|
||||||
|
err := a.Connect(context.Background(), peer.AddrInfo{ID: b.ID()})
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// waitForPeer waits for a to have 1 peer in the peerMap
|
||||||
|
func waitForPeer(t testing.TB, a *AutoNAT) {
|
||||||
|
t.Helper()
|
||||||
|
require.Eventually(t, func() bool {
|
||||||
|
a.mx.Lock()
|
||||||
|
defer a.mx.Unlock()
|
||||||
|
return a.peers.GetRand() != ""
|
||||||
|
}, 5*time.Second, 100*time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
// idAndWait provides server address and protocol to client
|
||||||
|
func idAndWait(t testing.TB, cli *AutoNAT, srv *AutoNAT) {
|
||||||
|
idAndConnect(t, cli.host, srv.host)
|
||||||
|
waitForPeer(t, cli)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAutoNATPrivateAddr(t *testing.T) {
|
||||||
|
an := newAutoNAT(t, nil)
|
||||||
|
res, err := an.GetReachability(context.Background(), []Request{{Addr: ma.StringCast("/ip4/192.168.0.1/udp/10/quic-v1")}})
|
||||||
|
require.Equal(t, res, Result{})
|
||||||
|
require.Contains(t, err.Error(), "private address cannot be verified by autonatv2")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClientRequest(t *testing.T) {
|
||||||
|
an := newAutoNAT(t, nil, allowPrivateAddrs)
|
||||||
|
defer an.Close()
|
||||||
|
defer an.host.Close()
|
||||||
|
|
||||||
|
b := bhost.NewBlankHost(swarmt.GenSwarm(t))
|
||||||
|
defer b.Close()
|
||||||
|
idAndConnect(t, an.host, b)
|
||||||
|
waitForPeer(t, an)
|
||||||
|
|
||||||
|
addrs := an.host.Addrs()
|
||||||
|
addrbs := make([][]byte, len(addrs))
|
||||||
|
for i := 0; i < len(addrs); i++ {
|
||||||
|
addrbs[i] = addrs[i].Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
var receivedRequest atomic.Bool
|
||||||
|
b.SetStreamHandler(DialProtocol, func(s network.Stream) {
|
||||||
|
receivedRequest.Store(true)
|
||||||
|
r := pbio.NewDelimitedReader(s, maxMsgSize)
|
||||||
|
var msg pb.Message
|
||||||
|
assert.NoError(t, r.ReadMsg(&msg))
|
||||||
|
assert.NotNil(t, msg.GetDialRequest())
|
||||||
|
assert.Equal(t, addrbs, msg.GetDialRequest().Addrs)
|
||||||
|
s.Reset()
|
||||||
|
})
|
||||||
|
|
||||||
|
res, err := an.GetReachability(context.Background(), []Request{
|
||||||
|
{Addr: addrs[0], SendDialData: true}, {Addr: addrs[1]},
|
||||||
|
})
|
||||||
|
require.Equal(t, res, Result{})
|
||||||
|
require.NotNil(t, err)
|
||||||
|
require.True(t, receivedRequest.Load())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClientServerError(t *testing.T) {
|
||||||
|
an := newAutoNAT(t, nil, allowPrivateAddrs)
|
||||||
|
defer an.Close()
|
||||||
|
defer an.host.Close()
|
||||||
|
|
||||||
|
b := bhost.NewBlankHost(swarmt.GenSwarm(t))
|
||||||
|
defer b.Close()
|
||||||
|
idAndConnect(t, an.host, b)
|
||||||
|
waitForPeer(t, an)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
handler func(network.Stream)
|
||||||
|
errorStr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
handler: func(s network.Stream) {
|
||||||
|
s.Reset()
|
||||||
|
},
|
||||||
|
errorStr: "stream reset",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
handler: func(s network.Stream) {
|
||||||
|
w := pbio.NewDelimitedWriter(s)
|
||||||
|
assert.NoError(t, w.WriteMsg(
|
||||||
|
&pb.Message{Msg: &pb.Message_DialRequest{DialRequest: &pb.DialRequest{}}}))
|
||||||
|
},
|
||||||
|
errorStr: "invalid msg type",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
handler: func(s network.Stream) {
|
||||||
|
w := pbio.NewDelimitedWriter(s)
|
||||||
|
assert.NoError(t, w.WriteMsg(
|
||||||
|
&pb.Message{Msg: &pb.Message_DialResponse{
|
||||||
|
DialResponse: &pb.DialResponse{
|
||||||
|
Status: pb.DialResponse_E_DIAL_REFUSED,
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
))
|
||||||
|
},
|
||||||
|
errorStr: ErrDialRefused.Error(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range tests {
|
||||||
|
t.Run(fmt.Sprintf("test-%d", i), func(t *testing.T) {
|
||||||
|
b.SetStreamHandler(DialProtocol, tc.handler)
|
||||||
|
addrs := an.host.Addrs()
|
||||||
|
res, err := an.GetReachability(
|
||||||
|
context.Background(),
|
||||||
|
newTestRequests(addrs, false))
|
||||||
|
require.Equal(t, res, Result{})
|
||||||
|
require.NotNil(t, err)
|
||||||
|
require.Contains(t, err.Error(), tc.errorStr)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClientDataRequest(t *testing.T) {
|
||||||
|
an := newAutoNAT(t, nil, allowPrivateAddrs)
|
||||||
|
defer an.Close()
|
||||||
|
defer an.host.Close()
|
||||||
|
|
||||||
|
b := bhost.NewBlankHost(swarmt.GenSwarm(t))
|
||||||
|
defer b.Close()
|
||||||
|
idAndConnect(t, an.host, b)
|
||||||
|
waitForPeer(t, an)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
handler func(network.Stream)
|
||||||
|
name string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "provides dial data",
|
||||||
|
handler: func(s network.Stream) {
|
||||||
|
r := pbio.NewDelimitedReader(s, maxMsgSize)
|
||||||
|
var msg pb.Message
|
||||||
|
assert.NoError(t, r.ReadMsg(&msg))
|
||||||
|
w := pbio.NewDelimitedWriter(s)
|
||||||
|
if err := w.WriteMsg(&pb.Message{
|
||||||
|
Msg: &pb.Message_DialDataRequest{
|
||||||
|
DialDataRequest: &pb.DialDataRequest{
|
||||||
|
AddrIdx: 0,
|
||||||
|
NumBytes: 10000,
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
s.Reset()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var dialData []byte
|
||||||
|
for len(dialData) < 10000 {
|
||||||
|
if err := r.ReadMsg(&msg); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
s.Reset()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if msg.GetDialDataResponse() == nil {
|
||||||
|
t.Errorf("expected to receive msg of type DialDataResponse")
|
||||||
|
s.Reset()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dialData = append(dialData, msg.GetDialDataResponse().Data...)
|
||||||
|
}
|
||||||
|
s.Reset()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "low priority addr",
|
||||||
|
handler: func(s network.Stream) {
|
||||||
|
r := pbio.NewDelimitedReader(s, maxMsgSize)
|
||||||
|
var msg pb.Message
|
||||||
|
assert.NoError(t, r.ReadMsg(&msg))
|
||||||
|
w := pbio.NewDelimitedWriter(s)
|
||||||
|
if err := w.WriteMsg(&pb.Message{
|
||||||
|
Msg: &pb.Message_DialDataRequest{
|
||||||
|
DialDataRequest: &pb.DialDataRequest{
|
||||||
|
AddrIdx: 1,
|
||||||
|
NumBytes: 10000,
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
s.Reset()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
assert.Error(t, r.ReadMsg(&msg))
|
||||||
|
s.Reset()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "too high dial data request",
|
||||||
|
handler: func(s network.Stream) {
|
||||||
|
r := pbio.NewDelimitedReader(s, maxMsgSize)
|
||||||
|
var msg pb.Message
|
||||||
|
assert.NoError(t, r.ReadMsg(&msg))
|
||||||
|
w := pbio.NewDelimitedWriter(s)
|
||||||
|
if err := w.WriteMsg(&pb.Message{
|
||||||
|
Msg: &pb.Message_DialDataRequest{
|
||||||
|
DialDataRequest: &pb.DialDataRequest{
|
||||||
|
AddrIdx: 0,
|
||||||
|
NumBytes: 1 << 32,
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
s.Reset()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
assert.Error(t, r.ReadMsg(&msg))
|
||||||
|
s.Reset()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
b.SetStreamHandler(DialProtocol, tc.handler)
|
||||||
|
addrs := an.host.Addrs()
|
||||||
|
|
||||||
|
res, err := an.GetReachability(
|
||||||
|
context.Background(),
|
||||||
|
[]Request{
|
||||||
|
{Addr: addrs[0], SendDialData: true},
|
||||||
|
{Addr: addrs[1]},
|
||||||
|
})
|
||||||
|
require.Equal(t, res, Result{})
|
||||||
|
require.NotNil(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClientDialBacks(t *testing.T) {
|
||||||
|
an := newAutoNAT(t, nil, allowPrivateAddrs)
|
||||||
|
defer an.Close()
|
||||||
|
defer an.host.Close()
|
||||||
|
|
||||||
|
b := bhost.NewBlankHost(swarmt.GenSwarm(t))
|
||||||
|
defer b.Close()
|
||||||
|
idAndConnect(t, an.host, b)
|
||||||
|
waitForPeer(t, an)
|
||||||
|
|
||||||
|
dialerHost := bhost.NewBlankHost(swarmt.GenSwarm(t))
|
||||||
|
defer dialerHost.Close()
|
||||||
|
|
||||||
|
readReq := func(r pbio.Reader) ([]ma.Multiaddr, uint64, error) {
|
||||||
|
var msg pb.Message
|
||||||
|
if err := r.ReadMsg(&msg); err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
if msg.GetDialRequest() == nil {
|
||||||
|
return nil, 0, errors.New("no dial request in msg")
|
||||||
|
}
|
||||||
|
addrs := parseAddrs(t, &msg)
|
||||||
|
return addrs, msg.GetDialRequest().GetNonce(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
writeNonce := func(addr ma.Multiaddr, nonce uint64) error {
|
||||||
|
pid := an.host.ID()
|
||||||
|
dialerHost.Peerstore().AddAddr(pid, addr, peerstore.PermanentAddrTTL)
|
||||||
|
defer func() {
|
||||||
|
dialerHost.Network().ClosePeer(pid)
|
||||||
|
dialerHost.Peerstore().RemovePeer(pid)
|
||||||
|
dialerHost.Peerstore().ClearAddrs(pid)
|
||||||
|
}()
|
||||||
|
as, err := dialerHost.NewStream(context.Background(), pid, DialBackProtocol)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
w := pbio.NewDelimitedWriter(as)
|
||||||
|
if err := w.WriteMsg(&pb.DialBack{Nonce: nonce}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
as.CloseWrite()
|
||||||
|
data := make([]byte, 1)
|
||||||
|
as.Read(data)
|
||||||
|
as.Close()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
handler func(network.Stream)
|
||||||
|
success bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "correct dial attempt",
|
||||||
|
handler: func(s network.Stream) {
|
||||||
|
r := pbio.NewDelimitedReader(s, maxMsgSize)
|
||||||
|
w := pbio.NewDelimitedWriter(s)
|
||||||
|
|
||||||
|
addrs, nonce, err := readReq(r)
|
||||||
|
if err != nil {
|
||||||
|
s.Reset()
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := writeNonce(addrs[1], nonce); err != nil {
|
||||||
|
s.Reset()
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.WriteMsg(&pb.Message{
|
||||||
|
Msg: &pb.Message_DialResponse{
|
||||||
|
DialResponse: &pb.DialResponse{
|
||||||
|
Status: pb.DialResponse_OK,
|
||||||
|
DialStatus: pb.DialStatus_OK,
|
||||||
|
AddrIdx: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
s.Close()
|
||||||
|
},
|
||||||
|
success: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no dial attempt",
|
||||||
|
handler: func(s network.Stream) {
|
||||||
|
r := pbio.NewDelimitedReader(s, maxMsgSize)
|
||||||
|
if _, _, err := readReq(r); err != nil {
|
||||||
|
s.Reset()
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp := &pb.DialResponse{
|
||||||
|
Status: pb.DialResponse_OK,
|
||||||
|
DialStatus: pb.DialStatus_OK,
|
||||||
|
AddrIdx: 0,
|
||||||
|
}
|
||||||
|
w := pbio.NewDelimitedWriter(s)
|
||||||
|
w.WriteMsg(&pb.Message{
|
||||||
|
Msg: &pb.Message_DialResponse{
|
||||||
|
DialResponse: resp,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
s.Close()
|
||||||
|
},
|
||||||
|
success: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid reported address",
|
||||||
|
handler: func(s network.Stream) {
|
||||||
|
r := pbio.NewDelimitedReader(s, maxMsgSize)
|
||||||
|
addrs, nonce, err := readReq(r)
|
||||||
|
if err != nil {
|
||||||
|
s.Reset()
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := writeNonce(addrs[1], nonce); err != nil {
|
||||||
|
s.Reset()
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w := pbio.NewDelimitedWriter(s)
|
||||||
|
w.WriteMsg(&pb.Message{
|
||||||
|
Msg: &pb.Message_DialResponse{
|
||||||
|
DialResponse: &pb.DialResponse{
|
||||||
|
Status: pb.DialResponse_OK,
|
||||||
|
DialStatus: pb.DialStatus_OK,
|
||||||
|
AddrIdx: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
s.Close()
|
||||||
|
},
|
||||||
|
success: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid nonce",
|
||||||
|
handler: func(s network.Stream) {
|
||||||
|
r := pbio.NewDelimitedReader(s, maxMsgSize)
|
||||||
|
addrs, nonce, err := readReq(r)
|
||||||
|
if err != nil {
|
||||||
|
s.Reset()
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := writeNonce(addrs[0], nonce-1); err != nil {
|
||||||
|
s.Reset()
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w := pbio.NewDelimitedWriter(s)
|
||||||
|
w.WriteMsg(&pb.Message{
|
||||||
|
Msg: &pb.Message_DialResponse{
|
||||||
|
DialResponse: &pb.DialResponse{
|
||||||
|
Status: pb.DialResponse_OK,
|
||||||
|
DialStatus: pb.DialStatus_OK,
|
||||||
|
AddrIdx: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
s.Close()
|
||||||
|
},
|
||||||
|
success: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid addr index",
|
||||||
|
handler: func(s network.Stream) {
|
||||||
|
r := pbio.NewDelimitedReader(s, maxMsgSize)
|
||||||
|
_, _, err := readReq(r)
|
||||||
|
if err != nil {
|
||||||
|
s.Reset()
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w := pbio.NewDelimitedWriter(s)
|
||||||
|
w.WriteMsg(&pb.Message{
|
||||||
|
Msg: &pb.Message_DialResponse{
|
||||||
|
DialResponse: &pb.DialResponse{
|
||||||
|
Status: pb.DialResponse_OK,
|
||||||
|
DialStatus: pb.DialStatus_OK,
|
||||||
|
AddrIdx: 10,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
s.Close()
|
||||||
|
},
|
||||||
|
success: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
addrs := an.host.Addrs()
|
||||||
|
b.SetStreamHandler(DialProtocol, tc.handler)
|
||||||
|
res, err := an.GetReachability(
|
||||||
|
context.Background(),
|
||||||
|
[]Request{
|
||||||
|
{Addr: addrs[0], SendDialData: true},
|
||||||
|
{Addr: addrs[1]},
|
||||||
|
})
|
||||||
|
if !tc.success {
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Equal(t, Result{}, res)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, res.Reachability, network.ReachabilityPublic)
|
||||||
|
require.Equal(t, res.Status, pb.DialStatus_OK)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEventSubscription(t *testing.T) {
|
||||||
|
an := newAutoNAT(t, nil)
|
||||||
|
defer an.host.Close()
|
||||||
|
|
||||||
|
b := bhost.NewBlankHost(swarmt.GenSwarm(t))
|
||||||
|
defer b.Close()
|
||||||
|
c := bhost.NewBlankHost(swarmt.GenSwarm(t))
|
||||||
|
defer c.Close()
|
||||||
|
|
||||||
|
idAndConnect(t, an.host, b)
|
||||||
|
require.Eventually(t, func() bool {
|
||||||
|
an.mx.Lock()
|
||||||
|
defer an.mx.Unlock()
|
||||||
|
return len(an.peers.peers) == 1
|
||||||
|
}, 5*time.Second, 100*time.Millisecond)
|
||||||
|
|
||||||
|
idAndConnect(t, an.host, c)
|
||||||
|
require.Eventually(t, func() bool {
|
||||||
|
an.mx.Lock()
|
||||||
|
defer an.mx.Unlock()
|
||||||
|
return len(an.peers.peers) == 2
|
||||||
|
}, 5*time.Second, 100*time.Millisecond)
|
||||||
|
|
||||||
|
an.host.Network().ClosePeer(b.ID())
|
||||||
|
require.Eventually(t, func() bool {
|
||||||
|
an.mx.Lock()
|
||||||
|
defer an.mx.Unlock()
|
||||||
|
return len(an.peers.peers) == 1
|
||||||
|
}, 5*time.Second, 100*time.Millisecond)
|
||||||
|
|
||||||
|
an.host.Network().ClosePeer(c.ID())
|
||||||
|
require.Eventually(t, func() bool {
|
||||||
|
an.mx.Lock()
|
||||||
|
defer an.mx.Unlock()
|
||||||
|
return len(an.peers.peers) == 0
|
||||||
|
}, 5*time.Second, 100*time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPeersMap(t *testing.T) {
|
||||||
|
emptyPeerID := peer.ID("")
|
||||||
|
|
||||||
|
t.Run("single_item", func(t *testing.T) {
|
||||||
|
p := newPeersMap()
|
||||||
|
p.Put("peer1")
|
||||||
|
p.Delete("peer1")
|
||||||
|
p.Put("peer1")
|
||||||
|
require.Equal(t, peer.ID("peer1"), p.GetRand())
|
||||||
|
p.Delete("peer1")
|
||||||
|
require.Equal(t, emptyPeerID, p.GetRand())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("multiple_items", func(t *testing.T) {
|
||||||
|
p := newPeersMap()
|
||||||
|
require.Equal(t, emptyPeerID, p.GetRand())
|
||||||
|
|
||||||
|
allPeers := make(map[peer.ID]bool)
|
||||||
|
for i := 0; i < 20; i++ {
|
||||||
|
pid := peer.ID(fmt.Sprintf("peer-%d", i))
|
||||||
|
allPeers[pid] = true
|
||||||
|
p.Put(pid)
|
||||||
|
}
|
||||||
|
foundPeers := make(map[peer.ID]bool)
|
||||||
|
for i := 0; i < 1000; i++ {
|
||||||
|
pid := p.GetRand()
|
||||||
|
require.NotEqual(t, emptyPeerID, p)
|
||||||
|
require.True(t, allPeers[pid])
|
||||||
|
foundPeers[pid] = true
|
||||||
|
if len(foundPeers) == len(allPeers) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for pid := range allPeers {
|
||||||
|
p.Delete(pid)
|
||||||
|
}
|
||||||
|
require.Equal(t, emptyPeerID, p.GetRand())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAreAddrsConsistency(t *testing.T) {
|
||||||
|
c := &client{
|
||||||
|
normalizeMultiaddr: func(a ma.Multiaddr) ma.Multiaddr {
|
||||||
|
for {
|
||||||
|
rest, l := ma.SplitLast(a)
|
||||||
|
if _, err := l.ValueForProtocol(ma.P_CERTHASH); err != nil {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
a = rest
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
localAddr ma.Multiaddr
|
||||||
|
dialAddr ma.Multiaddr
|
||||||
|
success bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "simple match",
|
||||||
|
localAddr: ma.StringCast("/ip4/192.168.0.1/tcp/12345"),
|
||||||
|
dialAddr: ma.StringCast("/ip4/1.2.3.4/tcp/23232"),
|
||||||
|
success: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nat64",
|
||||||
|
localAddr: ma.StringCast("/ip6/1::1/tcp/12345"),
|
||||||
|
dialAddr: ma.StringCast("/ip4/1.2.3.4/tcp/23232"),
|
||||||
|
success: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "simple mismatch",
|
||||||
|
localAddr: ma.StringCast("/ip4/192.168.0.1/tcp/12345"),
|
||||||
|
dialAddr: ma.StringCast("/ip4/1.2.3.4/udp/23232/quic-v1"),
|
||||||
|
success: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "quic-vs-webtransport",
|
||||||
|
localAddr: ma.StringCast("/ip4/192.168.0.1/udp/12345/quic-v1"),
|
||||||
|
dialAddr: ma.StringCast("/ip4/1.2.3.4/udp/123/quic-v1/webtransport"),
|
||||||
|
success: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "webtransport-certhash",
|
||||||
|
localAddr: ma.StringCast("/ip4/192.168.0.1/udp/12345/quic-v1/webtransport"),
|
||||||
|
dialAddr: ma.StringCast("/ip4/1.2.3.4/udp/123/quic-v1/webtransport/certhash/uEgNmb28"),
|
||||||
|
success: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "dns",
|
||||||
|
localAddr: ma.StringCast("/dns/lib.p2p/udp/12345/quic-v1"),
|
||||||
|
dialAddr: ma.StringCast("/ip6/1::1/udp/123/quic-v1/"),
|
||||||
|
success: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
if c.areAddrsConsistent(tc.localAddr, tc.dialAddr) != tc.success {
|
||||||
|
wantStr := "match"
|
||||||
|
if !tc.success {
|
||||||
|
wantStr = "mismatch"
|
||||||
|
}
|
||||||
|
t.Errorf("expected %s between\nlocal addr: %s\ndial addr: %s", wantStr, tc.localAddr, tc.dialAddr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
342
p2p/protocol/autonatv2/client.go
Normal file
342
p2p/protocol/autonatv2/client.go
Normal file
@@ -0,0 +1,342 @@
|
|||||||
|
package autonatv2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/libp2p/go-libp2p/core/host"
|
||||||
|
"github.com/libp2p/go-libp2p/core/network"
|
||||||
|
"github.com/libp2p/go-libp2p/core/peer"
|
||||||
|
"github.com/libp2p/go-libp2p/p2p/protocol/autonatv2/pb"
|
||||||
|
"github.com/libp2p/go-msgio/pbio"
|
||||||
|
ma "github.com/multiformats/go-multiaddr"
|
||||||
|
"golang.org/x/exp/rand"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:generate protoc --go_out=. --go_opt=Mpb/autonatv2.proto=./pb pb/autonatv2.proto
|
||||||
|
|
||||||
|
// client implements the client for making dial requests for AutoNAT v2. It verifies successful
|
||||||
|
// dials and provides an option to send data for dial requests.
|
||||||
|
type client struct {
|
||||||
|
host host.Host
|
||||||
|
dialData []byte
|
||||||
|
normalizeMultiaddr func(ma.Multiaddr) ma.Multiaddr
|
||||||
|
|
||||||
|
mu sync.Mutex
|
||||||
|
// dialBackQueues maps nonce to the channel for providing the local multiaddr of the connection
|
||||||
|
// the nonce was received on
|
||||||
|
dialBackQueues map[uint64]chan ma.Multiaddr
|
||||||
|
}
|
||||||
|
|
||||||
|
type normalizeMultiaddrer interface {
|
||||||
|
NormalizeMultiaddr(ma.Multiaddr) ma.Multiaddr
|
||||||
|
}
|
||||||
|
|
||||||
|
func newClient(h host.Host) *client {
|
||||||
|
normalizeMultiaddr := func(a ma.Multiaddr) ma.Multiaddr { return a }
|
||||||
|
if hn, ok := h.(normalizeMultiaddrer); ok {
|
||||||
|
normalizeMultiaddr = hn.NormalizeMultiaddr
|
||||||
|
}
|
||||||
|
return &client{
|
||||||
|
host: h,
|
||||||
|
dialData: make([]byte, 4000),
|
||||||
|
normalizeMultiaddr: normalizeMultiaddr,
|
||||||
|
dialBackQueues: make(map[uint64]chan ma.Multiaddr),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ac *client) Start() {
|
||||||
|
ac.host.SetStreamHandler(DialBackProtocol, ac.handleDialBack)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ac *client) Close() {
|
||||||
|
ac.host.RemoveStreamHandler(DialBackProtocol)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetReachability verifies address reachability with a AutoNAT v2 server p.
|
||||||
|
func (ac *client) GetReachability(ctx context.Context, p peer.ID, reqs []Request) (Result, error) {
|
||||||
|
ctx, cancel := context.WithTimeout(ctx, streamTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
s, err := ac.host.NewStream(ctx, p, DialProtocol)
|
||||||
|
if err != nil {
|
||||||
|
return Result{}, fmt.Errorf("open %s stream failed: %w", DialProtocol, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.Scope().SetService(ServiceName); err != nil {
|
||||||
|
s.Reset()
|
||||||
|
return Result{}, fmt.Errorf("attach stream %s to service %s failed: %w", DialProtocol, ServiceName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.Scope().ReserveMemory(maxMsgSize, network.ReservationPriorityAlways); err != nil {
|
||||||
|
s.Reset()
|
||||||
|
return Result{}, fmt.Errorf("failed to reserve memory for stream %s: %w", DialProtocol, err)
|
||||||
|
}
|
||||||
|
defer s.Scope().ReleaseMemory(maxMsgSize)
|
||||||
|
|
||||||
|
s.SetDeadline(time.Now().Add(streamTimeout))
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
nonce := rand.Uint64()
|
||||||
|
ch := make(chan ma.Multiaddr, 1)
|
||||||
|
ac.mu.Lock()
|
||||||
|
ac.dialBackQueues[nonce] = ch
|
||||||
|
ac.mu.Unlock()
|
||||||
|
defer func() {
|
||||||
|
ac.mu.Lock()
|
||||||
|
delete(ac.dialBackQueues, nonce)
|
||||||
|
ac.mu.Unlock()
|
||||||
|
}()
|
||||||
|
|
||||||
|
msg := newDialRequest(reqs, nonce)
|
||||||
|
w := pbio.NewDelimitedWriter(s)
|
||||||
|
if err := w.WriteMsg(&msg); err != nil {
|
||||||
|
s.Reset()
|
||||||
|
return Result{}, fmt.Errorf("dial request write failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
r := pbio.NewDelimitedReader(s, maxMsgSize)
|
||||||
|
if err := r.ReadMsg(&msg); err != nil {
|
||||||
|
s.Reset()
|
||||||
|
return Result{}, fmt.Errorf("dial msg read failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case msg.GetDialResponse() != nil:
|
||||||
|
break
|
||||||
|
// provide dial data if appropriate
|
||||||
|
case msg.GetDialDataRequest() != nil:
|
||||||
|
if err := ac.validateDialDataRequest(reqs, &msg); err != nil {
|
||||||
|
s.Reset()
|
||||||
|
return Result{}, fmt.Errorf("invalid dial data request: %w", err)
|
||||||
|
}
|
||||||
|
// dial data request is valid and we want to send data
|
||||||
|
if err := sendDialData(ac.dialData, int(msg.GetDialDataRequest().GetNumBytes()), w, &msg); err != nil {
|
||||||
|
s.Reset()
|
||||||
|
return Result{}, fmt.Errorf("dial data send failed: %w", err)
|
||||||
|
}
|
||||||
|
if err := r.ReadMsg(&msg); err != nil {
|
||||||
|
s.Reset()
|
||||||
|
return Result{}, fmt.Errorf("dial response read failed: %w", err)
|
||||||
|
}
|
||||||
|
if msg.GetDialResponse() == nil {
|
||||||
|
s.Reset()
|
||||||
|
return Result{}, fmt.Errorf("invalid response type: %T", msg.Msg)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
s.Reset()
|
||||||
|
return Result{}, fmt.Errorf("invalid msg type: %T", msg.Msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := msg.GetDialResponse()
|
||||||
|
if resp.GetStatus() != pb.DialResponse_OK {
|
||||||
|
// E_DIAL_REFUSED has implication for deciding future address verificiation priorities
|
||||||
|
// wrap a distinct error for convenient errors.Is usage
|
||||||
|
if resp.GetStatus() == pb.DialResponse_E_DIAL_REFUSED {
|
||||||
|
return Result{}, fmt.Errorf("dial request failed: %w", ErrDialRefused)
|
||||||
|
}
|
||||||
|
return Result{}, fmt.Errorf("dial request failed: response status %d %s", resp.GetStatus(),
|
||||||
|
pb.DialResponse_ResponseStatus_name[int32(resp.GetStatus())])
|
||||||
|
}
|
||||||
|
if resp.GetDialStatus() == pb.DialStatus_UNUSED {
|
||||||
|
return Result{}, fmt.Errorf("invalid response: invalid dial status UNUSED")
|
||||||
|
}
|
||||||
|
if int(resp.AddrIdx) >= len(reqs) {
|
||||||
|
return Result{}, fmt.Errorf("invalid response: addr index out of range: %d [0-%d)", resp.AddrIdx, len(reqs))
|
||||||
|
}
|
||||||
|
|
||||||
|
// wait for nonce from the server
|
||||||
|
var dialBackAddr ma.Multiaddr
|
||||||
|
if resp.GetDialStatus() == pb.DialStatus_OK {
|
||||||
|
timer := time.NewTimer(dialBackStreamTimeout)
|
||||||
|
select {
|
||||||
|
case at := <-ch:
|
||||||
|
dialBackAddr = at
|
||||||
|
case <-ctx.Done():
|
||||||
|
case <-timer.C:
|
||||||
|
}
|
||||||
|
timer.Stop()
|
||||||
|
}
|
||||||
|
return ac.newResult(resp, reqs, dialBackAddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ac *client) validateDialDataRequest(reqs []Request, msg *pb.Message) error {
|
||||||
|
idx := int(msg.GetDialDataRequest().AddrIdx)
|
||||||
|
if idx >= len(reqs) { // invalid address index
|
||||||
|
return fmt.Errorf("addr index out of range: %d [0-%d)", idx, len(reqs))
|
||||||
|
}
|
||||||
|
if msg.GetDialDataRequest().NumBytes > maxHandshakeSizeBytes { // data request is too high
|
||||||
|
return fmt.Errorf("requested data too high: %d", msg.GetDialDataRequest().NumBytes)
|
||||||
|
}
|
||||||
|
if !reqs[idx].SendDialData { // low priority addr
|
||||||
|
return fmt.Errorf("low priority addr: %s index %d", reqs[idx].Addr, idx)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ac *client) newResult(resp *pb.DialResponse, reqs []Request, dialBackAddr ma.Multiaddr) (Result, error) {
|
||||||
|
idx := int(resp.AddrIdx)
|
||||||
|
addr := reqs[idx].Addr
|
||||||
|
|
||||||
|
var rch network.Reachability
|
||||||
|
switch resp.DialStatus {
|
||||||
|
case pb.DialStatus_OK:
|
||||||
|
if !ac.areAddrsConsistent(dialBackAddr, addr) {
|
||||||
|
// the server is misinforming us about the address it successfully dialed
|
||||||
|
// either we received no dialback or the address on the dialback is inconsistent with
|
||||||
|
// what the server is telling us
|
||||||
|
return Result{}, fmt.Errorf("invalid response: dialBackAddr: %s, respAddr: %s", dialBackAddr, addr)
|
||||||
|
}
|
||||||
|
rch = network.ReachabilityPublic
|
||||||
|
case pb.DialStatus_E_DIAL_ERROR:
|
||||||
|
rch = network.ReachabilityPrivate
|
||||||
|
case pb.DialStatus_E_DIAL_BACK_ERROR:
|
||||||
|
if ac.areAddrsConsistent(dialBackAddr, addr) {
|
||||||
|
// We received the dial back but the server claims the dial back errored.
|
||||||
|
// As long as we received the correct nonce in dial back it is safe to assume
|
||||||
|
// that we are public.
|
||||||
|
rch = network.ReachabilityPublic
|
||||||
|
} else {
|
||||||
|
rch = network.ReachabilityUnknown
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// Unexpected response code. Discard the response and fail.
|
||||||
|
log.Warnf("invalid status code received in response for addr %s: %d", addr, resp.DialStatus)
|
||||||
|
return Result{}, fmt.Errorf("invalid response: invalid status code for addr %s: %d", addr, resp.DialStatus)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result{
|
||||||
|
Addr: addr,
|
||||||
|
Reachability: rch,
|
||||||
|
Status: resp.DialStatus,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendDialData(dialData []byte, numBytes int, w pbio.Writer, msg *pb.Message) (err error) {
|
||||||
|
ddResp := &pb.DialDataResponse{Data: dialData}
|
||||||
|
*msg = pb.Message{
|
||||||
|
Msg: &pb.Message_DialDataResponse{
|
||||||
|
DialDataResponse: ddResp,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for remain := numBytes; remain > 0; {
|
||||||
|
if remain < len(ddResp.Data) {
|
||||||
|
ddResp.Data = ddResp.Data[:remain]
|
||||||
|
}
|
||||||
|
if err := w.WriteMsg(msg); err != nil {
|
||||||
|
return fmt.Errorf("write failed: %w", err)
|
||||||
|
}
|
||||||
|
remain -= len(dialData)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDialRequest(reqs []Request, nonce uint64) pb.Message {
|
||||||
|
addrbs := make([][]byte, len(reqs))
|
||||||
|
for i, r := range reqs {
|
||||||
|
addrbs[i] = r.Addr.Bytes()
|
||||||
|
}
|
||||||
|
return pb.Message{
|
||||||
|
Msg: &pb.Message_DialRequest{
|
||||||
|
DialRequest: &pb.DialRequest{
|
||||||
|
Addrs: addrbs,
|
||||||
|
Nonce: nonce,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleDialBack receives the nonce on the dial-back stream
|
||||||
|
func (ac *client) handleDialBack(s network.Stream) {
|
||||||
|
if err := s.Scope().SetService(ServiceName); err != nil {
|
||||||
|
log.Debugf("failed to attach stream to service %s: %w", ServiceName, err)
|
||||||
|
s.Reset()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.Scope().ReserveMemory(dialBackMaxMsgSize, network.ReservationPriorityAlways); err != nil {
|
||||||
|
log.Debugf("failed to reserve memory for stream %s: %w", DialBackProtocol, err)
|
||||||
|
s.Reset()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer s.Scope().ReleaseMemory(dialBackMaxMsgSize)
|
||||||
|
|
||||||
|
s.SetDeadline(time.Now().Add(dialBackStreamTimeout))
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
r := pbio.NewDelimitedReader(s, dialBackMaxMsgSize)
|
||||||
|
var msg pb.DialBack
|
||||||
|
if err := r.ReadMsg(&msg); err != nil {
|
||||||
|
log.Debugf("failed to read dialback msg from %s: %s", s.Conn().RemotePeer(), err)
|
||||||
|
s.Reset()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
nonce := msg.GetNonce()
|
||||||
|
|
||||||
|
ac.mu.Lock()
|
||||||
|
ch := ac.dialBackQueues[nonce]
|
||||||
|
ac.mu.Unlock()
|
||||||
|
if ch == nil {
|
||||||
|
log.Debugf("dialback received with invalid nonce: localAdds: %s peer: %s nonce: %d", s.Conn().LocalMultiaddr(), s.Conn().RemotePeer(), nonce)
|
||||||
|
s.Reset()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case ch <- s.Conn().LocalMultiaddr():
|
||||||
|
default:
|
||||||
|
log.Debugf("multiple dialbacks received: localAddr: %s peer: %s", s.Conn().LocalMultiaddr(), s.Conn().RemotePeer())
|
||||||
|
s.Reset()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w := pbio.NewDelimitedWriter(s)
|
||||||
|
res := pb.DialBackResponse{}
|
||||||
|
if err := w.WriteMsg(&res); err != nil {
|
||||||
|
log.Debugf("failed to write dialback response: %s", err)
|
||||||
|
s.Reset()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ac *client) areAddrsConsistent(connLocalAddr, dialedAddr ma.Multiaddr) bool {
|
||||||
|
if connLocalAddr == nil || dialedAddr == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
connLocalAddr = ac.normalizeMultiaddr(connLocalAddr)
|
||||||
|
dialedAddr = ac.normalizeMultiaddr(dialedAddr)
|
||||||
|
|
||||||
|
localProtos := connLocalAddr.Protocols()
|
||||||
|
externalProtos := dialedAddr.Protocols()
|
||||||
|
if len(localProtos) != len(externalProtos) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i := 0; i < len(localProtos); i++ {
|
||||||
|
if i == 0 {
|
||||||
|
switch externalProtos[i].Code {
|
||||||
|
case ma.P_DNS, ma.P_DNSADDR:
|
||||||
|
if localProtos[i].Code == ma.P_IP4 || localProtos[i].Code == ma.P_IP6 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
case ma.P_DNS4:
|
||||||
|
if localProtos[i].Code == ma.P_IP4 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
case ma.P_DNS6:
|
||||||
|
if localProtos[i].Code == ma.P_IP6 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if localProtos[i].Code != externalProtos[i].Code {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if localProtos[i].Code != externalProtos[i].Code {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
38
p2p/protocol/autonatv2/msg_reader.go
Normal file
38
p2p/protocol/autonatv2/msg_reader.go
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package autonatv2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/multiformats/go-varint"
|
||||||
|
)
|
||||||
|
|
||||||
|
// msgReader reads a varint prefixed message from R without any buffering
|
||||||
|
type msgReader struct {
|
||||||
|
R io.Reader
|
||||||
|
Buf []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *msgReader) ReadByte() (byte, error) {
|
||||||
|
buf := m.Buf[:1]
|
||||||
|
_, err := m.R.Read(buf)
|
||||||
|
return buf[0], err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *msgReader) ReadMsg() ([]byte, error) {
|
||||||
|
sz, err := varint.ReadUvarint(m)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if sz > uint64(len(m.Buf)) {
|
||||||
|
return nil, io.ErrShortBuffer
|
||||||
|
}
|
||||||
|
n := 0
|
||||||
|
for n < int(sz) {
|
||||||
|
nr, err := m.R.Read(m.Buf[n:sz])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
n += nr
|
||||||
|
}
|
||||||
|
return m.Buf[:sz], nil
|
||||||
|
}
|
56
p2p/protocol/autonatv2/options.go
Normal file
56
p2p/protocol/autonatv2/options.go
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
package autonatv2
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// autoNATSettings is used to configure AutoNAT
|
||||||
|
type autoNATSettings struct {
|
||||||
|
allowPrivateAddrs bool
|
||||||
|
serverRPM int
|
||||||
|
serverPerPeerRPM int
|
||||||
|
serverDialDataRPM int
|
||||||
|
dataRequestPolicy dataRequestPolicyFunc
|
||||||
|
now func() time.Time
|
||||||
|
amplificatonAttackPreventionDialWait time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultSettings() *autoNATSettings {
|
||||||
|
return &autoNATSettings{
|
||||||
|
allowPrivateAddrs: false,
|
||||||
|
serverRPM: 60, // 1 every second
|
||||||
|
serverPerPeerRPM: 12, // 1 every 5 seconds
|
||||||
|
serverDialDataRPM: 12, // 1 every 5 seconds
|
||||||
|
dataRequestPolicy: amplificationAttackPrevention,
|
||||||
|
amplificatonAttackPreventionDialWait: 3 * time.Second,
|
||||||
|
now: time.Now,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type AutoNATOption func(s *autoNATSettings) error
|
||||||
|
|
||||||
|
func WithServerRateLimit(rpm, perPeerRPM, dialDataRPM int) AutoNATOption {
|
||||||
|
return func(s *autoNATSettings) error {
|
||||||
|
s.serverRPM = rpm
|
||||||
|
s.serverPerPeerRPM = perPeerRPM
|
||||||
|
s.serverDialDataRPM = dialDataRPM
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func withDataRequestPolicy(drp dataRequestPolicyFunc) AutoNATOption {
|
||||||
|
return func(s *autoNATSettings) error {
|
||||||
|
s.dataRequestPolicy = drp
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func allowPrivateAddrs(s *autoNATSettings) error {
|
||||||
|
s.allowPrivateAddrs = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func withAmplificationAttackPreventionDialWait(d time.Duration) AutoNATOption {
|
||||||
|
return func(s *autoNATSettings) error {
|
||||||
|
s.amplificatonAttackPreventionDialWait = d
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
818
p2p/protocol/autonatv2/pb/autonatv2.pb.go
Normal file
818
p2p/protocol/autonatv2/pb/autonatv2.pb.go
Normal file
@@ -0,0 +1,818 @@
|
|||||||
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// protoc-gen-go v1.30.0
|
||||||
|
// protoc v4.25.3
|
||||||
|
// source: pb/autonatv2.proto
|
||||||
|
|
||||||
|
package pb
|
||||||
|
|
||||||
|
import (
|
||||||
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
|
reflect "reflect"
|
||||||
|
sync "sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Verify that this generated code is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||||
|
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||||
|
)
|
||||||
|
|
||||||
|
type DialStatus int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
DialStatus_UNUSED DialStatus = 0
|
||||||
|
DialStatus_E_DIAL_ERROR DialStatus = 100
|
||||||
|
DialStatus_E_DIAL_BACK_ERROR DialStatus = 101
|
||||||
|
DialStatus_OK DialStatus = 200
|
||||||
|
)
|
||||||
|
|
||||||
|
// Enum value maps for DialStatus.
|
||||||
|
var (
|
||||||
|
DialStatus_name = map[int32]string{
|
||||||
|
0: "UNUSED",
|
||||||
|
100: "E_DIAL_ERROR",
|
||||||
|
101: "E_DIAL_BACK_ERROR",
|
||||||
|
200: "OK",
|
||||||
|
}
|
||||||
|
DialStatus_value = map[string]int32{
|
||||||
|
"UNUSED": 0,
|
||||||
|
"E_DIAL_ERROR": 100,
|
||||||
|
"E_DIAL_BACK_ERROR": 101,
|
||||||
|
"OK": 200,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (x DialStatus) Enum() *DialStatus {
|
||||||
|
p := new(DialStatus)
|
||||||
|
*p = x
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x DialStatus) String() string {
|
||||||
|
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (DialStatus) Descriptor() protoreflect.EnumDescriptor {
|
||||||
|
return file_pb_autonatv2_proto_enumTypes[0].Descriptor()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (DialStatus) Type() protoreflect.EnumType {
|
||||||
|
return &file_pb_autonatv2_proto_enumTypes[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x DialStatus) Number() protoreflect.EnumNumber {
|
||||||
|
return protoreflect.EnumNumber(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use DialStatus.Descriptor instead.
|
||||||
|
func (DialStatus) EnumDescriptor() ([]byte, []int) {
|
||||||
|
return file_pb_autonatv2_proto_rawDescGZIP(), []int{0}
|
||||||
|
}
|
||||||
|
|
||||||
|
type DialResponse_ResponseStatus int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
DialResponse_E_INTERNAL_ERROR DialResponse_ResponseStatus = 0
|
||||||
|
DialResponse_E_REQUEST_REJECTED DialResponse_ResponseStatus = 100
|
||||||
|
DialResponse_E_DIAL_REFUSED DialResponse_ResponseStatus = 101
|
||||||
|
DialResponse_OK DialResponse_ResponseStatus = 200
|
||||||
|
)
|
||||||
|
|
||||||
|
// Enum value maps for DialResponse_ResponseStatus.
|
||||||
|
var (
|
||||||
|
DialResponse_ResponseStatus_name = map[int32]string{
|
||||||
|
0: "E_INTERNAL_ERROR",
|
||||||
|
100: "E_REQUEST_REJECTED",
|
||||||
|
101: "E_DIAL_REFUSED",
|
||||||
|
200: "OK",
|
||||||
|
}
|
||||||
|
DialResponse_ResponseStatus_value = map[string]int32{
|
||||||
|
"E_INTERNAL_ERROR": 0,
|
||||||
|
"E_REQUEST_REJECTED": 100,
|
||||||
|
"E_DIAL_REFUSED": 101,
|
||||||
|
"OK": 200,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (x DialResponse_ResponseStatus) Enum() *DialResponse_ResponseStatus {
|
||||||
|
p := new(DialResponse_ResponseStatus)
|
||||||
|
*p = x
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x DialResponse_ResponseStatus) String() string {
|
||||||
|
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (DialResponse_ResponseStatus) Descriptor() protoreflect.EnumDescriptor {
|
||||||
|
return file_pb_autonatv2_proto_enumTypes[1].Descriptor()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (DialResponse_ResponseStatus) Type() protoreflect.EnumType {
|
||||||
|
return &file_pb_autonatv2_proto_enumTypes[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x DialResponse_ResponseStatus) Number() protoreflect.EnumNumber {
|
||||||
|
return protoreflect.EnumNumber(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use DialResponse_ResponseStatus.Descriptor instead.
|
||||||
|
func (DialResponse_ResponseStatus) EnumDescriptor() ([]byte, []int) {
|
||||||
|
return file_pb_autonatv2_proto_rawDescGZIP(), []int{3, 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
type DialBackResponse_DialBackStatus int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
DialBackResponse_OK DialBackResponse_DialBackStatus = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
// Enum value maps for DialBackResponse_DialBackStatus.
|
||||||
|
var (
|
||||||
|
DialBackResponse_DialBackStatus_name = map[int32]string{
|
||||||
|
0: "OK",
|
||||||
|
}
|
||||||
|
DialBackResponse_DialBackStatus_value = map[string]int32{
|
||||||
|
"OK": 0,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (x DialBackResponse_DialBackStatus) Enum() *DialBackResponse_DialBackStatus {
|
||||||
|
p := new(DialBackResponse_DialBackStatus)
|
||||||
|
*p = x
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x DialBackResponse_DialBackStatus) String() string {
|
||||||
|
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (DialBackResponse_DialBackStatus) Descriptor() protoreflect.EnumDescriptor {
|
||||||
|
return file_pb_autonatv2_proto_enumTypes[2].Descriptor()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (DialBackResponse_DialBackStatus) Type() protoreflect.EnumType {
|
||||||
|
return &file_pb_autonatv2_proto_enumTypes[2]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x DialBackResponse_DialBackStatus) Number() protoreflect.EnumNumber {
|
||||||
|
return protoreflect.EnumNumber(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use DialBackResponse_DialBackStatus.Descriptor instead.
|
||||||
|
func (DialBackResponse_DialBackStatus) EnumDescriptor() ([]byte, []int) {
|
||||||
|
return file_pb_autonatv2_proto_rawDescGZIP(), []int{6, 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Message struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
// Types that are assignable to Msg:
|
||||||
|
//
|
||||||
|
// *Message_DialRequest
|
||||||
|
// *Message_DialResponse
|
||||||
|
// *Message_DialDataRequest
|
||||||
|
// *Message_DialDataResponse
|
||||||
|
Msg isMessage_Msg `protobuf_oneof:"msg"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Message) Reset() {
|
||||||
|
*x = Message{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_pb_autonatv2_proto_msgTypes[0]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Message) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Message) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *Message) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_pb_autonatv2_proto_msgTypes[0]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use Message.ProtoReflect.Descriptor instead.
|
||||||
|
func (*Message) Descriptor() ([]byte, []int) {
|
||||||
|
return file_pb_autonatv2_proto_rawDescGZIP(), []int{0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Message) GetMsg() isMessage_Msg {
|
||||||
|
if m != nil {
|
||||||
|
return m.Msg
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Message) GetDialRequest() *DialRequest {
|
||||||
|
if x, ok := x.GetMsg().(*Message_DialRequest); ok {
|
||||||
|
return x.DialRequest
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Message) GetDialResponse() *DialResponse {
|
||||||
|
if x, ok := x.GetMsg().(*Message_DialResponse); ok {
|
||||||
|
return x.DialResponse
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Message) GetDialDataRequest() *DialDataRequest {
|
||||||
|
if x, ok := x.GetMsg().(*Message_DialDataRequest); ok {
|
||||||
|
return x.DialDataRequest
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Message) GetDialDataResponse() *DialDataResponse {
|
||||||
|
if x, ok := x.GetMsg().(*Message_DialDataResponse); ok {
|
||||||
|
return x.DialDataResponse
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type isMessage_Msg interface {
|
||||||
|
isMessage_Msg()
|
||||||
|
}
|
||||||
|
|
||||||
|
type Message_DialRequest struct {
|
||||||
|
DialRequest *DialRequest `protobuf:"bytes,1,opt,name=dialRequest,proto3,oneof"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Message_DialResponse struct {
|
||||||
|
DialResponse *DialResponse `protobuf:"bytes,2,opt,name=dialResponse,proto3,oneof"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Message_DialDataRequest struct {
|
||||||
|
DialDataRequest *DialDataRequest `protobuf:"bytes,3,opt,name=dialDataRequest,proto3,oneof"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Message_DialDataResponse struct {
|
||||||
|
DialDataResponse *DialDataResponse `protobuf:"bytes,4,opt,name=dialDataResponse,proto3,oneof"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Message_DialRequest) isMessage_Msg() {}
|
||||||
|
|
||||||
|
func (*Message_DialResponse) isMessage_Msg() {}
|
||||||
|
|
||||||
|
func (*Message_DialDataRequest) isMessage_Msg() {}
|
||||||
|
|
||||||
|
func (*Message_DialDataResponse) isMessage_Msg() {}
|
||||||
|
|
||||||
|
type DialRequest struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Addrs [][]byte `protobuf:"bytes,1,rep,name=addrs,proto3" json:"addrs,omitempty"`
|
||||||
|
Nonce uint64 `protobuf:"fixed64,2,opt,name=nonce,proto3" json:"nonce,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DialRequest) Reset() {
|
||||||
|
*x = DialRequest{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_pb_autonatv2_proto_msgTypes[1]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DialRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*DialRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *DialRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_pb_autonatv2_proto_msgTypes[1]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use DialRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*DialRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_pb_autonatv2_proto_rawDescGZIP(), []int{1}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DialRequest) GetAddrs() [][]byte {
|
||||||
|
if x != nil {
|
||||||
|
return x.Addrs
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DialRequest) GetNonce() uint64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.Nonce
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
type DialDataRequest struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
AddrIdx uint32 `protobuf:"varint,1,opt,name=addrIdx,proto3" json:"addrIdx,omitempty"`
|
||||||
|
NumBytes uint64 `protobuf:"varint,2,opt,name=numBytes,proto3" json:"numBytes,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DialDataRequest) Reset() {
|
||||||
|
*x = DialDataRequest{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_pb_autonatv2_proto_msgTypes[2]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DialDataRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*DialDataRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *DialDataRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_pb_autonatv2_proto_msgTypes[2]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use DialDataRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*DialDataRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_pb_autonatv2_proto_rawDescGZIP(), []int{2}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DialDataRequest) GetAddrIdx() uint32 {
|
||||||
|
if x != nil {
|
||||||
|
return x.AddrIdx
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DialDataRequest) GetNumBytes() uint64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.NumBytes
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
type DialResponse struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Status DialResponse_ResponseStatus `protobuf:"varint,1,opt,name=status,proto3,enum=autonatv2.pb.DialResponse_ResponseStatus" json:"status,omitempty"`
|
||||||
|
AddrIdx uint32 `protobuf:"varint,2,opt,name=addrIdx,proto3" json:"addrIdx,omitempty"`
|
||||||
|
DialStatus DialStatus `protobuf:"varint,3,opt,name=dialStatus,proto3,enum=autonatv2.pb.DialStatus" json:"dialStatus,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DialResponse) Reset() {
|
||||||
|
*x = DialResponse{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_pb_autonatv2_proto_msgTypes[3]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DialResponse) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*DialResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *DialResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_pb_autonatv2_proto_msgTypes[3]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use DialResponse.ProtoReflect.Descriptor instead.
|
||||||
|
func (*DialResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return file_pb_autonatv2_proto_rawDescGZIP(), []int{3}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DialResponse) GetStatus() DialResponse_ResponseStatus {
|
||||||
|
if x != nil {
|
||||||
|
return x.Status
|
||||||
|
}
|
||||||
|
return DialResponse_E_INTERNAL_ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DialResponse) GetAddrIdx() uint32 {
|
||||||
|
if x != nil {
|
||||||
|
return x.AddrIdx
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DialResponse) GetDialStatus() DialStatus {
|
||||||
|
if x != nil {
|
||||||
|
return x.DialStatus
|
||||||
|
}
|
||||||
|
return DialStatus_UNUSED
|
||||||
|
}
|
||||||
|
|
||||||
|
type DialDataResponse struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DialDataResponse) Reset() {
|
||||||
|
*x = DialDataResponse{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_pb_autonatv2_proto_msgTypes[4]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DialDataResponse) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*DialDataResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *DialDataResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_pb_autonatv2_proto_msgTypes[4]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use DialDataResponse.ProtoReflect.Descriptor instead.
|
||||||
|
func (*DialDataResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return file_pb_autonatv2_proto_rawDescGZIP(), []int{4}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DialDataResponse) GetData() []byte {
|
||||||
|
if x != nil {
|
||||||
|
return x.Data
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type DialBack struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Nonce uint64 `protobuf:"fixed64,1,opt,name=nonce,proto3" json:"nonce,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DialBack) Reset() {
|
||||||
|
*x = DialBack{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_pb_autonatv2_proto_msgTypes[5]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DialBack) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*DialBack) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *DialBack) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_pb_autonatv2_proto_msgTypes[5]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use DialBack.ProtoReflect.Descriptor instead.
|
||||||
|
func (*DialBack) Descriptor() ([]byte, []int) {
|
||||||
|
return file_pb_autonatv2_proto_rawDescGZIP(), []int{5}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DialBack) GetNonce() uint64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.Nonce
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
type DialBackResponse struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Status DialBackResponse_DialBackStatus `protobuf:"varint,1,opt,name=status,proto3,enum=autonatv2.pb.DialBackResponse_DialBackStatus" json:"status,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DialBackResponse) Reset() {
|
||||||
|
*x = DialBackResponse{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_pb_autonatv2_proto_msgTypes[6]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DialBackResponse) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*DialBackResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *DialBackResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_pb_autonatv2_proto_msgTypes[6]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use DialBackResponse.ProtoReflect.Descriptor instead.
|
||||||
|
func (*DialBackResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return file_pb_autonatv2_proto_rawDescGZIP(), []int{6}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DialBackResponse) GetStatus() DialBackResponse_DialBackStatus {
|
||||||
|
if x != nil {
|
||||||
|
return x.Status
|
||||||
|
}
|
||||||
|
return DialBackResponse_OK
|
||||||
|
}
|
||||||
|
|
||||||
|
var File_pb_autonatv2_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
|
var file_pb_autonatv2_proto_rawDesc = []byte{
|
||||||
|
0x0a, 0x12, 0x70, 0x62, 0x2f, 0x61, 0x75, 0x74, 0x6f, 0x6e, 0x61, 0x74, 0x76, 0x32, 0x2e, 0x70,
|
||||||
|
0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0c, 0x61, 0x75, 0x74, 0x6f, 0x6e, 0x61, 0x74, 0x76, 0x32, 0x2e,
|
||||||
|
0x70, 0x62, 0x22, 0xaa, 0x02, 0x0a, 0x07, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x3d,
|
||||||
|
0x0a, 0x0b, 0x64, 0x69, 0x61, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x01, 0x20,
|
||||||
|
0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x61, 0x75, 0x74, 0x6f, 0x6e, 0x61, 0x74, 0x76, 0x32, 0x2e,
|
||||||
|
0x70, 0x62, 0x2e, 0x44, 0x69, 0x61, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00,
|
||||||
|
0x52, 0x0b, 0x64, 0x69, 0x61, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x40, 0x0a,
|
||||||
|
0x0c, 0x64, 0x69, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x02, 0x20,
|
||||||
|
0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x61, 0x75, 0x74, 0x6f, 0x6e, 0x61, 0x74, 0x76, 0x32, 0x2e,
|
||||||
|
0x70, 0x62, 0x2e, 0x44, 0x69, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48,
|
||||||
|
0x00, 0x52, 0x0c, 0x64, 0x69, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
|
||||||
|
0x49, 0x0a, 0x0f, 0x64, 0x69, 0x61, 0x6c, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||||
|
0x73, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x61, 0x75, 0x74, 0x6f, 0x6e,
|
||||||
|
0x61, 0x74, 0x76, 0x32, 0x2e, 0x70, 0x62, 0x2e, 0x44, 0x69, 0x61, 0x6c, 0x44, 0x61, 0x74, 0x61,
|
||||||
|
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x0f, 0x64, 0x69, 0x61, 0x6c, 0x44,
|
||||||
|
0x61, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x4c, 0x0a, 0x10, 0x64, 0x69,
|
||||||
|
0x61, 0x6c, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x04,
|
||||||
|
0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x61, 0x75, 0x74, 0x6f, 0x6e, 0x61, 0x74, 0x76, 0x32,
|
||||||
|
0x2e, 0x70, 0x62, 0x2e, 0x44, 0x69, 0x61, 0x6c, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70,
|
||||||
|
0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x10, 0x64, 0x69, 0x61, 0x6c, 0x44, 0x61, 0x74, 0x61,
|
||||||
|
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x05, 0x0a, 0x03, 0x6d, 0x73, 0x67, 0x22,
|
||||||
|
0x39, 0x0a, 0x0b, 0x44, 0x69, 0x61, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14,
|
||||||
|
0x0a, 0x05, 0x61, 0x64, 0x64, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x05, 0x61,
|
||||||
|
0x64, 0x64, 0x72, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x18, 0x02, 0x20,
|
||||||
|
0x01, 0x28, 0x06, 0x52, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x22, 0x47, 0x0a, 0x0f, 0x44, 0x69,
|
||||||
|
0x61, 0x6c, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a,
|
||||||
|
0x07, 0x61, 0x64, 0x64, 0x72, 0x49, 0x64, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07,
|
||||||
|
0x61, 0x64, 0x64, 0x72, 0x49, 0x64, 0x78, 0x12, 0x1a, 0x0a, 0x08, 0x6e, 0x75, 0x6d, 0x42, 0x79,
|
||||||
|
0x74, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x6e, 0x75, 0x6d, 0x42, 0x79,
|
||||||
|
0x74, 0x65, 0x73, 0x22, 0x82, 0x02, 0x0a, 0x0c, 0x44, 0x69, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x70,
|
||||||
|
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01,
|
||||||
|
0x20, 0x01, 0x28, 0x0e, 0x32, 0x29, 0x2e, 0x61, 0x75, 0x74, 0x6f, 0x6e, 0x61, 0x74, 0x76, 0x32,
|
||||||
|
0x2e, 0x70, 0x62, 0x2e, 0x44, 0x69, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
|
||||||
|
0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52,
|
||||||
|
0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x49,
|
||||||
|
0x64, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x49, 0x64,
|
||||||
|
0x78, 0x12, 0x38, 0x0a, 0x0a, 0x64, 0x69, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18,
|
||||||
|
0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x61, 0x75, 0x74, 0x6f, 0x6e, 0x61, 0x74, 0x76,
|
||||||
|
0x32, 0x2e, 0x70, 0x62, 0x2e, 0x44, 0x69, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52,
|
||||||
|
0x0a, 0x64, 0x69, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x5b, 0x0a, 0x0e, 0x52,
|
||||||
|
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x14, 0x0a,
|
||||||
|
0x10, 0x45, 0x5f, 0x49, 0x4e, 0x54, 0x45, 0x52, 0x4e, 0x41, 0x4c, 0x5f, 0x45, 0x52, 0x52, 0x4f,
|
||||||
|
0x52, 0x10, 0x00, 0x12, 0x16, 0x0a, 0x12, 0x45, 0x5f, 0x52, 0x45, 0x51, 0x55, 0x45, 0x53, 0x54,
|
||||||
|
0x5f, 0x52, 0x45, 0x4a, 0x45, 0x43, 0x54, 0x45, 0x44, 0x10, 0x64, 0x12, 0x12, 0x0a, 0x0e, 0x45,
|
||||||
|
0x5f, 0x44, 0x49, 0x41, 0x4c, 0x5f, 0x52, 0x45, 0x46, 0x55, 0x53, 0x45, 0x44, 0x10, 0x65, 0x12,
|
||||||
|
0x07, 0x0a, 0x02, 0x4f, 0x4b, 0x10, 0xc8, 0x01, 0x22, 0x26, 0x0a, 0x10, 0x44, 0x69, 0x61, 0x6c,
|
||||||
|
0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04,
|
||||||
|
0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61,
|
||||||
|
0x22, 0x20, 0x0a, 0x08, 0x44, 0x69, 0x61, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x12, 0x14, 0x0a, 0x05,
|
||||||
|
0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x06, 0x52, 0x05, 0x6e, 0x6f, 0x6e,
|
||||||
|
0x63, 0x65, 0x22, 0x73, 0x0a, 0x10, 0x44, 0x69, 0x61, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x52, 0x65,
|
||||||
|
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x45, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73,
|
||||||
|
0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2d, 0x2e, 0x61, 0x75, 0x74, 0x6f, 0x6e, 0x61, 0x74,
|
||||||
|
0x76, 0x32, 0x2e, 0x70, 0x62, 0x2e, 0x44, 0x69, 0x61, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x52, 0x65,
|
||||||
|
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x44, 0x69, 0x61, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x53,
|
||||||
|
0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x18, 0x0a,
|
||||||
|
0x0e, 0x44, 0x69, 0x61, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12,
|
||||||
|
0x06, 0x0a, 0x02, 0x4f, 0x4b, 0x10, 0x00, 0x2a, 0x4a, 0x0a, 0x0a, 0x44, 0x69, 0x61, 0x6c, 0x53,
|
||||||
|
0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0a, 0x0a, 0x06, 0x55, 0x4e, 0x55, 0x53, 0x45, 0x44, 0x10,
|
||||||
|
0x00, 0x12, 0x10, 0x0a, 0x0c, 0x45, 0x5f, 0x44, 0x49, 0x41, 0x4c, 0x5f, 0x45, 0x52, 0x52, 0x4f,
|
||||||
|
0x52, 0x10, 0x64, 0x12, 0x15, 0x0a, 0x11, 0x45, 0x5f, 0x44, 0x49, 0x41, 0x4c, 0x5f, 0x42, 0x41,
|
||||||
|
0x43, 0x4b, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x65, 0x12, 0x07, 0x0a, 0x02, 0x4f, 0x4b,
|
||||||
|
0x10, 0xc8, 0x01, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
file_pb_autonatv2_proto_rawDescOnce sync.Once
|
||||||
|
file_pb_autonatv2_proto_rawDescData = file_pb_autonatv2_proto_rawDesc
|
||||||
|
)
|
||||||
|
|
||||||
|
func file_pb_autonatv2_proto_rawDescGZIP() []byte {
|
||||||
|
file_pb_autonatv2_proto_rawDescOnce.Do(func() {
|
||||||
|
file_pb_autonatv2_proto_rawDescData = protoimpl.X.CompressGZIP(file_pb_autonatv2_proto_rawDescData)
|
||||||
|
})
|
||||||
|
return file_pb_autonatv2_proto_rawDescData
|
||||||
|
}
|
||||||
|
|
||||||
|
var file_pb_autonatv2_proto_enumTypes = make([]protoimpl.EnumInfo, 3)
|
||||||
|
var file_pb_autonatv2_proto_msgTypes = make([]protoimpl.MessageInfo, 7)
|
||||||
|
var file_pb_autonatv2_proto_goTypes = []interface{}{
|
||||||
|
(DialStatus)(0), // 0: autonatv2.pb.DialStatus
|
||||||
|
(DialResponse_ResponseStatus)(0), // 1: autonatv2.pb.DialResponse.ResponseStatus
|
||||||
|
(DialBackResponse_DialBackStatus)(0), // 2: autonatv2.pb.DialBackResponse.DialBackStatus
|
||||||
|
(*Message)(nil), // 3: autonatv2.pb.Message
|
||||||
|
(*DialRequest)(nil), // 4: autonatv2.pb.DialRequest
|
||||||
|
(*DialDataRequest)(nil), // 5: autonatv2.pb.DialDataRequest
|
||||||
|
(*DialResponse)(nil), // 6: autonatv2.pb.DialResponse
|
||||||
|
(*DialDataResponse)(nil), // 7: autonatv2.pb.DialDataResponse
|
||||||
|
(*DialBack)(nil), // 8: autonatv2.pb.DialBack
|
||||||
|
(*DialBackResponse)(nil), // 9: autonatv2.pb.DialBackResponse
|
||||||
|
}
|
||||||
|
var file_pb_autonatv2_proto_depIdxs = []int32{
|
||||||
|
4, // 0: autonatv2.pb.Message.dialRequest:type_name -> autonatv2.pb.DialRequest
|
||||||
|
6, // 1: autonatv2.pb.Message.dialResponse:type_name -> autonatv2.pb.DialResponse
|
||||||
|
5, // 2: autonatv2.pb.Message.dialDataRequest:type_name -> autonatv2.pb.DialDataRequest
|
||||||
|
7, // 3: autonatv2.pb.Message.dialDataResponse:type_name -> autonatv2.pb.DialDataResponse
|
||||||
|
1, // 4: autonatv2.pb.DialResponse.status:type_name -> autonatv2.pb.DialResponse.ResponseStatus
|
||||||
|
0, // 5: autonatv2.pb.DialResponse.dialStatus:type_name -> autonatv2.pb.DialStatus
|
||||||
|
2, // 6: autonatv2.pb.DialBackResponse.status:type_name -> autonatv2.pb.DialBackResponse.DialBackStatus
|
||||||
|
7, // [7:7] is the sub-list for method output_type
|
||||||
|
7, // [7:7] is the sub-list for method input_type
|
||||||
|
7, // [7:7] is the sub-list for extension type_name
|
||||||
|
7, // [7:7] is the sub-list for extension extendee
|
||||||
|
0, // [0:7] is the sub-list for field type_name
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() { file_pb_autonatv2_proto_init() }
|
||||||
|
func file_pb_autonatv2_proto_init() {
|
||||||
|
if File_pb_autonatv2_proto != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !protoimpl.UnsafeEnabled {
|
||||||
|
file_pb_autonatv2_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*Message); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_pb_autonatv2_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*DialRequest); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_pb_autonatv2_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*DialDataRequest); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_pb_autonatv2_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*DialResponse); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_pb_autonatv2_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*DialDataResponse); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_pb_autonatv2_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*DialBack); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_pb_autonatv2_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*DialBackResponse); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_pb_autonatv2_proto_msgTypes[0].OneofWrappers = []interface{}{
|
||||||
|
(*Message_DialRequest)(nil),
|
||||||
|
(*Message_DialResponse)(nil),
|
||||||
|
(*Message_DialDataRequest)(nil),
|
||||||
|
(*Message_DialDataResponse)(nil),
|
||||||
|
}
|
||||||
|
type x struct{}
|
||||||
|
out := protoimpl.TypeBuilder{
|
||||||
|
File: protoimpl.DescBuilder{
|
||||||
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
|
RawDescriptor: file_pb_autonatv2_proto_rawDesc,
|
||||||
|
NumEnums: 3,
|
||||||
|
NumMessages: 7,
|
||||||
|
NumExtensions: 0,
|
||||||
|
NumServices: 0,
|
||||||
|
},
|
||||||
|
GoTypes: file_pb_autonatv2_proto_goTypes,
|
||||||
|
DependencyIndexes: file_pb_autonatv2_proto_depIdxs,
|
||||||
|
EnumInfos: file_pb_autonatv2_proto_enumTypes,
|
||||||
|
MessageInfos: file_pb_autonatv2_proto_msgTypes,
|
||||||
|
}.Build()
|
||||||
|
File_pb_autonatv2_proto = out.File
|
||||||
|
file_pb_autonatv2_proto_rawDesc = nil
|
||||||
|
file_pb_autonatv2_proto_goTypes = nil
|
||||||
|
file_pb_autonatv2_proto_depIdxs = nil
|
||||||
|
}
|
64
p2p/protocol/autonatv2/pb/autonatv2.proto
Normal file
64
p2p/protocol/autonatv2/pb/autonatv2.proto
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package autonatv2.pb;
|
||||||
|
|
||||||
|
message Message {
|
||||||
|
oneof msg {
|
||||||
|
DialRequest dialRequest = 1;
|
||||||
|
DialResponse dialResponse = 2;
|
||||||
|
DialDataRequest dialDataRequest = 3;
|
||||||
|
DialDataResponse dialDataResponse = 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message DialRequest {
|
||||||
|
repeated bytes addrs = 1;
|
||||||
|
fixed64 nonce = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
message DialDataRequest {
|
||||||
|
uint32 addrIdx = 1;
|
||||||
|
uint64 numBytes = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
enum DialStatus {
|
||||||
|
UNUSED = 0;
|
||||||
|
E_DIAL_ERROR = 100;
|
||||||
|
E_DIAL_BACK_ERROR = 101;
|
||||||
|
OK = 200;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
message DialResponse {
|
||||||
|
enum ResponseStatus {
|
||||||
|
E_INTERNAL_ERROR = 0;
|
||||||
|
E_REQUEST_REJECTED = 100;
|
||||||
|
E_DIAL_REFUSED = 101;
|
||||||
|
OK = 200;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResponseStatus status = 1;
|
||||||
|
uint32 addrIdx = 2;
|
||||||
|
DialStatus dialStatus = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
message DialDataResponse {
|
||||||
|
bytes data = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
message DialBack {
|
||||||
|
fixed64 nonce = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
message DialBackResponse {
|
||||||
|
enum DialBackStatus {
|
||||||
|
OK = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
DialBackStatus status = 1;
|
||||||
|
}
|
449
p2p/protocol/autonatv2/server.go
Normal file
449
p2p/protocol/autonatv2/server.go
Normal file
@@ -0,0 +1,449 @@
|
|||||||
|
package autonatv2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
pool "github.com/libp2p/go-buffer-pool"
|
||||||
|
"github.com/libp2p/go-libp2p/core/host"
|
||||||
|
"github.com/libp2p/go-libp2p/core/network"
|
||||||
|
"github.com/libp2p/go-libp2p/core/peer"
|
||||||
|
"github.com/libp2p/go-libp2p/core/peerstore"
|
||||||
|
"github.com/libp2p/go-libp2p/p2p/protocol/autonatv2/pb"
|
||||||
|
"github.com/libp2p/go-msgio/pbio"
|
||||||
|
|
||||||
|
"math/rand"
|
||||||
|
|
||||||
|
ma "github.com/multiformats/go-multiaddr"
|
||||||
|
manet "github.com/multiformats/go-multiaddr/net"
|
||||||
|
)
|
||||||
|
|
||||||
|
type dataRequestPolicyFunc = func(s network.Stream, dialAddr ma.Multiaddr) bool
|
||||||
|
|
||||||
|
// server implements the AutoNATv2 server.
|
||||||
|
// It can ask client to provide dial data before attempting the requested dial.
|
||||||
|
// It rate limits requests on a global level, per peer level and on whether the request requires dial data.
|
||||||
|
type server struct {
|
||||||
|
host host.Host
|
||||||
|
dialerHost host.Host
|
||||||
|
limiter *rateLimiter
|
||||||
|
|
||||||
|
// dialDataRequestPolicy is used to determine whether dialing the address requires receiving
|
||||||
|
// dial data. It is set to amplification attack prevention by default.
|
||||||
|
dialDataRequestPolicy dataRequestPolicyFunc
|
||||||
|
amplificatonAttackPreventionDialWait time.Duration
|
||||||
|
|
||||||
|
// for tests
|
||||||
|
now func() time.Time
|
||||||
|
allowPrivateAddrs bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func newServer(host, dialer host.Host, s *autoNATSettings) *server {
|
||||||
|
return &server{
|
||||||
|
dialerHost: dialer,
|
||||||
|
host: host,
|
||||||
|
dialDataRequestPolicy: s.dataRequestPolicy,
|
||||||
|
amplificatonAttackPreventionDialWait: s.amplificatonAttackPreventionDialWait,
|
||||||
|
allowPrivateAddrs: s.allowPrivateAddrs,
|
||||||
|
limiter: &rateLimiter{
|
||||||
|
RPM: s.serverRPM,
|
||||||
|
PerPeerRPM: s.serverPerPeerRPM,
|
||||||
|
DialDataRPM: s.serverDialDataRPM,
|
||||||
|
now: s.now,
|
||||||
|
},
|
||||||
|
now: s.now,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable attaches the stream handler to the host.
|
||||||
|
func (as *server) Start() {
|
||||||
|
as.host.SetStreamHandler(DialProtocol, as.handleDialRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (as *server) Close() {
|
||||||
|
as.host.RemoveStreamHandler(DialProtocol)
|
||||||
|
as.dialerHost.Close()
|
||||||
|
as.limiter.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleDialRequest is the dial-request protocol stream handler
|
||||||
|
func (as *server) handleDialRequest(s network.Stream) {
|
||||||
|
if err := s.Scope().SetService(ServiceName); err != nil {
|
||||||
|
s.Reset()
|
||||||
|
log.Debugf("failed to attach stream to service %s: %w", ServiceName, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.Scope().ReserveMemory(maxMsgSize, network.ReservationPriorityAlways); err != nil {
|
||||||
|
s.Reset()
|
||||||
|
log.Debugf("failed to reserve memory for stream %s: %w", DialProtocol, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer s.Scope().ReleaseMemory(maxMsgSize)
|
||||||
|
|
||||||
|
deadline := as.now().Add(streamTimeout)
|
||||||
|
ctx, cancel := context.WithDeadline(context.Background(), deadline)
|
||||||
|
defer cancel()
|
||||||
|
s.SetDeadline(as.now().Add(streamTimeout))
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
p := s.Conn().RemotePeer()
|
||||||
|
|
||||||
|
var msg pb.Message
|
||||||
|
w := pbio.NewDelimitedWriter(s)
|
||||||
|
// Check for rate limit before parsing the request
|
||||||
|
if !as.limiter.Accept(p) {
|
||||||
|
msg = pb.Message{
|
||||||
|
Msg: &pb.Message_DialResponse{
|
||||||
|
DialResponse: &pb.DialResponse{
|
||||||
|
Status: pb.DialResponse_E_REQUEST_REJECTED,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if err := w.WriteMsg(&msg); err != nil {
|
||||||
|
s.Reset()
|
||||||
|
log.Debugf("failed to write request rejected response to %s: %s", p, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Debugf("rejected request from %s: rate limit exceeded", p)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer as.limiter.CompleteRequest(p)
|
||||||
|
|
||||||
|
r := pbio.NewDelimitedReader(s, maxMsgSize)
|
||||||
|
if err := r.ReadMsg(&msg); err != nil {
|
||||||
|
s.Reset()
|
||||||
|
log.Debugf("failed to read request from %s: %s", p, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if msg.GetDialRequest() == nil {
|
||||||
|
s.Reset()
|
||||||
|
log.Debugf("invalid message type from %s: %T expected: DialRequest", p, msg.Msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse peer's addresses
|
||||||
|
var dialAddr ma.Multiaddr
|
||||||
|
var addrIdx int
|
||||||
|
for i, ab := range msg.GetDialRequest().GetAddrs() {
|
||||||
|
if i >= maxPeerAddresses {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
a, err := ma.NewMultiaddrBytes(ab)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !as.allowPrivateAddrs && !manet.IsPublicAddr(a) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !as.dialerHost.Network().CanDial(p, a) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
dialAddr = a
|
||||||
|
addrIdx = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// No dialable address
|
||||||
|
if dialAddr == nil {
|
||||||
|
msg = pb.Message{
|
||||||
|
Msg: &pb.Message_DialResponse{
|
||||||
|
DialResponse: &pb.DialResponse{
|
||||||
|
Status: pb.DialResponse_E_DIAL_REFUSED,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if err := w.WriteMsg(&msg); err != nil {
|
||||||
|
s.Reset()
|
||||||
|
log.Debugf("failed to write dial refused response to %s: %s", p, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
nonce := msg.GetDialRequest().Nonce
|
||||||
|
|
||||||
|
isDialDataRequired := as.dialDataRequestPolicy(s, dialAddr)
|
||||||
|
if isDialDataRequired && !as.limiter.AcceptDialDataRequest(p) {
|
||||||
|
msg = pb.Message{
|
||||||
|
Msg: &pb.Message_DialResponse{
|
||||||
|
DialResponse: &pb.DialResponse{
|
||||||
|
Status: pb.DialResponse_E_REQUEST_REJECTED,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if err := w.WriteMsg(&msg); err != nil {
|
||||||
|
s.Reset()
|
||||||
|
log.Debugf("failed to write request rejected response to %s: %s", p, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Debugf("rejected request from %s: rate limit exceeded", p)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if isDialDataRequired {
|
||||||
|
if err := getDialData(w, s, &msg, addrIdx); err != nil {
|
||||||
|
s.Reset()
|
||||||
|
log.Debugf("%s refused dial data request: %s", p, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// wait for a bit to prevent thundering herd style attacks on a victim
|
||||||
|
waitTime := time.Duration(rand.Intn(int(as.amplificatonAttackPreventionDialWait) + 1)) // the range is [0, n)
|
||||||
|
t := time.NewTimer(waitTime)
|
||||||
|
defer t.Stop()
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
s.Reset()
|
||||||
|
log.Debugf("rejecting request without dialing: %s %p ", p, ctx.Err())
|
||||||
|
return
|
||||||
|
case <-t.C:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dialStatus := as.dialBack(ctx, s.Conn().RemotePeer(), dialAddr, nonce)
|
||||||
|
msg = pb.Message{
|
||||||
|
Msg: &pb.Message_DialResponse{
|
||||||
|
DialResponse: &pb.DialResponse{
|
||||||
|
Status: pb.DialResponse_OK,
|
||||||
|
DialStatus: dialStatus,
|
||||||
|
AddrIdx: uint32(addrIdx),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if err := w.WriteMsg(&msg); err != nil {
|
||||||
|
s.Reset()
|
||||||
|
log.Debugf("failed to write response to %s: %s", p, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getDialData gets data from the client for dialing the address
|
||||||
|
func getDialData(w pbio.Writer, s network.Stream, msg *pb.Message, addrIdx int) error {
|
||||||
|
numBytes := minHandshakeSizeBytes + rand.Intn(maxHandshakeSizeBytes-minHandshakeSizeBytes)
|
||||||
|
*msg = pb.Message{
|
||||||
|
Msg: &pb.Message_DialDataRequest{
|
||||||
|
DialDataRequest: &pb.DialDataRequest{
|
||||||
|
AddrIdx: uint32(addrIdx),
|
||||||
|
NumBytes: uint64(numBytes),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if err := w.WriteMsg(msg); err != nil {
|
||||||
|
return fmt.Errorf("dial data write: %w", err)
|
||||||
|
}
|
||||||
|
// pbio.Reader that we used so far on this stream is buffered. But at this point
|
||||||
|
// there is nothing unread on the stream. So it is safe to use the raw stream to
|
||||||
|
// read, reducing allocations.
|
||||||
|
return readDialData(numBytes, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func readDialData(numBytes int, r io.Reader) error {
|
||||||
|
mr := &msgReader{R: r, Buf: pool.Get(maxMsgSize)}
|
||||||
|
defer pool.Put(mr.Buf)
|
||||||
|
for remain := numBytes; remain > 0; {
|
||||||
|
msg, err := mr.ReadMsg()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("dial data read: %w", err)
|
||||||
|
}
|
||||||
|
// protobuf format is:
|
||||||
|
// (oneof dialDataResponse:<fieldTag><len varint>)(dial data:<fieldTag><len varint><bytes>)
|
||||||
|
bytesLen := len(msg)
|
||||||
|
bytesLen -= 2 // fieldTag + varint first byte
|
||||||
|
if bytesLen > 127 {
|
||||||
|
bytesLen -= 1 // varint second byte
|
||||||
|
}
|
||||||
|
bytesLen -= 2 // second fieldTag + varint first byte
|
||||||
|
if bytesLen > 127 {
|
||||||
|
bytesLen -= 1 // varint second byte
|
||||||
|
}
|
||||||
|
if bytesLen > 0 {
|
||||||
|
remain -= bytesLen
|
||||||
|
}
|
||||||
|
// Check if the peer is not sending too little data forcing us to just do a lot of compute
|
||||||
|
if bytesLen < 100 && remain > 0 {
|
||||||
|
return fmt.Errorf("dial data msg too small: %d", bytesLen)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (as *server) dialBack(ctx context.Context, p peer.ID, addr ma.Multiaddr, nonce uint64) pb.DialStatus {
|
||||||
|
ctx, cancel := context.WithTimeout(ctx, dialBackDialTimeout)
|
||||||
|
ctx = network.WithForceDirectDial(ctx, "autonatv2")
|
||||||
|
as.dialerHost.Peerstore().AddAddr(p, addr, peerstore.TempAddrTTL)
|
||||||
|
defer func() {
|
||||||
|
cancel()
|
||||||
|
as.dialerHost.Network().ClosePeer(p)
|
||||||
|
as.dialerHost.Peerstore().ClearAddrs(p)
|
||||||
|
as.dialerHost.Peerstore().RemovePeer(p)
|
||||||
|
}()
|
||||||
|
|
||||||
|
err := as.dialerHost.Connect(ctx, peer.AddrInfo{ID: p})
|
||||||
|
if err != nil {
|
||||||
|
return pb.DialStatus_E_DIAL_ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
s, err := as.dialerHost.NewStream(ctx, p, DialBackProtocol)
|
||||||
|
if err != nil {
|
||||||
|
return pb.DialStatus_E_DIAL_BACK_ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
defer s.Close()
|
||||||
|
s.SetDeadline(as.now().Add(dialBackStreamTimeout))
|
||||||
|
|
||||||
|
w := pbio.NewDelimitedWriter(s)
|
||||||
|
if err := w.WriteMsg(&pb.DialBack{Nonce: nonce}); err != nil {
|
||||||
|
s.Reset()
|
||||||
|
return pb.DialStatus_E_DIAL_BACK_ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since the underlying connection is on a separate dialer, it'll be closed after this
|
||||||
|
// function returns. Connection close will drop all the queued writes. To ensure message
|
||||||
|
// delivery, do a CloseWrite and read a byte from the stream. The peer actually sends a
|
||||||
|
// response of type DialBackResponse but we only care about the fact that the DialBack
|
||||||
|
// message has reached the peer. So we ignore that message on the read side.
|
||||||
|
s.CloseWrite()
|
||||||
|
s.SetDeadline(as.now().Add(5 * time.Second)) // 5 is a magic number
|
||||||
|
b := make([]byte, 1) // Read 1 byte here because 0 len reads are free to return (0, nil) immediately
|
||||||
|
s.Read(b)
|
||||||
|
|
||||||
|
return pb.DialStatus_OK
|
||||||
|
}
|
||||||
|
|
||||||
|
// rateLimiter implements a sliding window rate limit of requests per minute. It allows 1 concurrent request
|
||||||
|
// per peer. It rate limits requests globally, at a peer level and depending on whether it requires dial data.
|
||||||
|
type rateLimiter struct {
|
||||||
|
// PerPeerRPM is the rate limit per peer
|
||||||
|
PerPeerRPM int
|
||||||
|
// RPM is the global rate limit
|
||||||
|
RPM int
|
||||||
|
// DialDataRPM is the rate limit for requests that require dial data
|
||||||
|
DialDataRPM int
|
||||||
|
|
||||||
|
mu sync.Mutex
|
||||||
|
closed bool
|
||||||
|
reqs []entry
|
||||||
|
peerReqs map[peer.ID][]time.Time
|
||||||
|
dialDataReqs []time.Time
|
||||||
|
// ongoingReqs tracks in progress requests. This is used to disallow multiple concurrent requests by the
|
||||||
|
// same peer
|
||||||
|
// TODO: Should we allow a few concurrent requests per peer?
|
||||||
|
ongoingReqs map[peer.ID]struct{}
|
||||||
|
|
||||||
|
now func() time.Time // for tests
|
||||||
|
}
|
||||||
|
|
||||||
|
type entry struct {
|
||||||
|
PeerID peer.ID
|
||||||
|
Time time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *rateLimiter) Accept(p peer.ID) bool {
|
||||||
|
r.mu.Lock()
|
||||||
|
defer r.mu.Unlock()
|
||||||
|
if r.closed {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if r.peerReqs == nil {
|
||||||
|
r.peerReqs = make(map[peer.ID][]time.Time)
|
||||||
|
r.ongoingReqs = make(map[peer.ID]struct{})
|
||||||
|
}
|
||||||
|
|
||||||
|
nw := r.now()
|
||||||
|
r.cleanup(nw)
|
||||||
|
|
||||||
|
if _, ok := r.ongoingReqs[p]; ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if len(r.reqs) >= r.RPM || len(r.peerReqs[p]) >= r.PerPeerRPM {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
r.ongoingReqs[p] = struct{}{}
|
||||||
|
r.reqs = append(r.reqs, entry{PeerID: p, Time: nw})
|
||||||
|
r.peerReqs[p] = append(r.peerReqs[p], nw)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *rateLimiter) AcceptDialDataRequest(p peer.ID) bool {
|
||||||
|
r.mu.Lock()
|
||||||
|
defer r.mu.Unlock()
|
||||||
|
if r.closed {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if r.peerReqs == nil {
|
||||||
|
r.peerReqs = make(map[peer.ID][]time.Time)
|
||||||
|
r.ongoingReqs = make(map[peer.ID]struct{})
|
||||||
|
}
|
||||||
|
nw := r.now()
|
||||||
|
r.cleanup(nw)
|
||||||
|
if len(r.dialDataReqs) >= r.DialDataRPM {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
r.dialDataReqs = append(r.dialDataReqs, nw)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// cleanup removes stale requests.
|
||||||
|
//
|
||||||
|
// This is fast enough in rate limited cases and the state is small enough to
|
||||||
|
// clean up quickly when blocking requests.
|
||||||
|
func (r *rateLimiter) cleanup(now time.Time) {
|
||||||
|
idx := len(r.reqs)
|
||||||
|
for i, e := range r.reqs {
|
||||||
|
if now.Sub(e.Time) >= time.Minute {
|
||||||
|
pi := len(r.peerReqs[e.PeerID])
|
||||||
|
for j, t := range r.peerReqs[e.PeerID] {
|
||||||
|
if now.Sub(t) < time.Minute {
|
||||||
|
pi = j
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r.peerReqs[e.PeerID] = r.peerReqs[e.PeerID][pi:]
|
||||||
|
if len(r.peerReqs[e.PeerID]) == 0 {
|
||||||
|
delete(r.peerReqs, e.PeerID)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
idx = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r.reqs = r.reqs[idx:]
|
||||||
|
|
||||||
|
idx = len(r.dialDataReqs)
|
||||||
|
for i, t := range r.dialDataReqs {
|
||||||
|
if now.Sub(t) < time.Minute {
|
||||||
|
idx = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r.dialDataReqs = r.dialDataReqs[idx:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *rateLimiter) CompleteRequest(p peer.ID) {
|
||||||
|
r.mu.Lock()
|
||||||
|
defer r.mu.Unlock()
|
||||||
|
delete(r.ongoingReqs, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *rateLimiter) Close() {
|
||||||
|
r.mu.Lock()
|
||||||
|
defer r.mu.Unlock()
|
||||||
|
r.closed = true
|
||||||
|
r.peerReqs = nil
|
||||||
|
r.ongoingReqs = nil
|
||||||
|
r.dialDataReqs = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// amplificationAttackPrevention is a dialDataRequestPolicy which requests data when the peer's observed
|
||||||
|
// IP address is different from the dial back IP address
|
||||||
|
func amplificationAttackPrevention(s network.Stream, dialAddr ma.Multiaddr) bool {
|
||||||
|
connIP, err := manet.ToIP(s.Conn().RemoteMultiaddr())
|
||||||
|
if err != nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
dialIP, _ := manet.ToIP(s.Conn().LocalMultiaddr()) // must be an IP multiaddr
|
||||||
|
return !connIP.Equal(dialIP)
|
||||||
|
}
|
484
p2p/protocol/autonatv2/server_test.go
Normal file
484
p2p/protocol/autonatv2/server_test.go
Normal file
@@ -0,0 +1,484 @@
|
|||||||
|
package autonatv2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"math"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/libp2p/go-libp2p/core/network"
|
||||||
|
"github.com/libp2p/go-libp2p/core/peer"
|
||||||
|
"github.com/libp2p/go-libp2p/core/test"
|
||||||
|
bhost "github.com/libp2p/go-libp2p/p2p/host/blank"
|
||||||
|
"github.com/libp2p/go-libp2p/p2p/net/swarm"
|
||||||
|
swarmt "github.com/libp2p/go-libp2p/p2p/net/swarm/testing"
|
||||||
|
"github.com/libp2p/go-libp2p/p2p/protocol/autonatv2/pb"
|
||||||
|
"github.com/libp2p/go-msgio/pbio"
|
||||||
|
ma "github.com/multiformats/go-multiaddr"
|
||||||
|
"github.com/multiformats/go-varint"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newTestRequests(addrs []ma.Multiaddr, sendDialData bool) (reqs []Request) {
|
||||||
|
reqs = make([]Request, len(addrs))
|
||||||
|
for i := 0; i < len(addrs); i++ {
|
||||||
|
reqs[i] = Request{Addr: addrs[i], SendDialData: sendDialData}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServerInvalidAddrsRejected(t *testing.T) {
|
||||||
|
c := newAutoNAT(t, nil, allowPrivateAddrs, withAmplificationAttackPreventionDialWait(0))
|
||||||
|
defer c.Close()
|
||||||
|
defer c.host.Close()
|
||||||
|
|
||||||
|
t.Run("no transport", func(t *testing.T) {
|
||||||
|
dialer := bhost.NewBlankHost(swarmt.GenSwarm(t, swarmt.OptDisableQUIC, swarmt.OptDisableTCP))
|
||||||
|
an := newAutoNAT(t, dialer, allowPrivateAddrs)
|
||||||
|
defer an.Close()
|
||||||
|
defer an.host.Close()
|
||||||
|
|
||||||
|
idAndWait(t, c, an)
|
||||||
|
|
||||||
|
res, err := c.GetReachability(context.Background(), newTestRequests(c.host.Addrs(), true))
|
||||||
|
require.ErrorIs(t, err, ErrDialRefused)
|
||||||
|
require.Equal(t, Result{}, res)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("black holed addr", func(t *testing.T) {
|
||||||
|
dialer := bhost.NewBlankHost(swarmt.GenSwarm(
|
||||||
|
t, swarmt.WithSwarmOpts(swarm.WithReadOnlyBlackHoleDetector())))
|
||||||
|
an := newAutoNAT(t, dialer)
|
||||||
|
defer an.Close()
|
||||||
|
defer an.host.Close()
|
||||||
|
|
||||||
|
idAndWait(t, c, an)
|
||||||
|
|
||||||
|
res, err := c.GetReachability(context.Background(),
|
||||||
|
[]Request{{
|
||||||
|
Addr: ma.StringCast("/ip4/1.2.3.4/udp/1234/quic-v1"),
|
||||||
|
SendDialData: true,
|
||||||
|
}})
|
||||||
|
require.ErrorIs(t, err, ErrDialRefused)
|
||||||
|
require.Equal(t, Result{}, res)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("private addrs", func(t *testing.T) {
|
||||||
|
an := newAutoNAT(t, nil)
|
||||||
|
defer an.Close()
|
||||||
|
defer an.host.Close()
|
||||||
|
|
||||||
|
idAndWait(t, c, an)
|
||||||
|
|
||||||
|
res, err := c.GetReachability(context.Background(), newTestRequests(c.host.Addrs(), true))
|
||||||
|
require.ErrorIs(t, err, ErrDialRefused)
|
||||||
|
require.Equal(t, Result{}, res)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("relay addrs", func(t *testing.T) {
|
||||||
|
an := newAutoNAT(t, nil)
|
||||||
|
defer an.Close()
|
||||||
|
defer an.host.Close()
|
||||||
|
|
||||||
|
idAndWait(t, c, an)
|
||||||
|
|
||||||
|
res, err := c.GetReachability(context.Background(), newTestRequests(
|
||||||
|
[]ma.Multiaddr{ma.StringCast(fmt.Sprintf("/ip4/1.2.3.4/tcp/1/p2p/%s/p2p-circuit/p2p/%s", c.host.ID(), c.srv.dialerHost.ID()))}, true))
|
||||||
|
require.ErrorIs(t, err, ErrDialRefused)
|
||||||
|
require.Equal(t, Result{}, res)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("no addr", func(t *testing.T) {
|
||||||
|
_, err := c.GetReachability(context.Background(), nil)
|
||||||
|
require.Error(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("too many address", func(t *testing.T) {
|
||||||
|
dialer := bhost.NewBlankHost(swarmt.GenSwarm(t, swarmt.OptDisableTCP))
|
||||||
|
an := newAutoNAT(t, dialer, allowPrivateAddrs)
|
||||||
|
defer an.Close()
|
||||||
|
defer an.host.Close()
|
||||||
|
|
||||||
|
var addrs []ma.Multiaddr
|
||||||
|
for i := 0; i < 100; i++ {
|
||||||
|
addrs = append(addrs, ma.StringCast(fmt.Sprintf("/ip4/127.0.0.1/tcp/%d", 2000+i)))
|
||||||
|
}
|
||||||
|
addrs = append(addrs, c.host.Addrs()...)
|
||||||
|
// The dial should still fail because we have too many addresses that the server cannot dial
|
||||||
|
idAndWait(t, c, an)
|
||||||
|
|
||||||
|
res, err := c.GetReachability(context.Background(), newTestRequests(addrs, true))
|
||||||
|
require.ErrorIs(t, err, ErrDialRefused)
|
||||||
|
require.Equal(t, Result{}, res)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("msg too large", func(t *testing.T) {
|
||||||
|
dialer := bhost.NewBlankHost(swarmt.GenSwarm(t, swarmt.OptDisableTCP))
|
||||||
|
an := newAutoNAT(t, dialer, allowPrivateAddrs)
|
||||||
|
defer an.Close()
|
||||||
|
defer an.host.Close()
|
||||||
|
|
||||||
|
var addrs []ma.Multiaddr
|
||||||
|
for i := 0; i < 10000; i++ {
|
||||||
|
addrs = append(addrs, ma.StringCast(fmt.Sprintf("/ip4/127.0.0.1/tcp/%d", 2000+i)))
|
||||||
|
}
|
||||||
|
addrs = append(addrs, c.host.Addrs()...)
|
||||||
|
// The dial should still fail because we have too many addresses that the server cannot dial
|
||||||
|
idAndWait(t, c, an)
|
||||||
|
|
||||||
|
res, err := c.GetReachability(context.Background(), newTestRequests(addrs, true))
|
||||||
|
require.ErrorIs(t, err, network.ErrReset)
|
||||||
|
require.Equal(t, Result{}, res)
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServerDataRequest(t *testing.T) {
|
||||||
|
// server will skip all tcp addresses
|
||||||
|
dialer := bhost.NewBlankHost(swarmt.GenSwarm(t, swarmt.OptDisableTCP))
|
||||||
|
// ask for dial data for quic address
|
||||||
|
an := newAutoNAT(t, dialer, allowPrivateAddrs, withDataRequestPolicy(
|
||||||
|
func(s network.Stream, dialAddr ma.Multiaddr) bool {
|
||||||
|
if _, err := dialAddr.ValueForProtocol(ma.P_QUIC_V1); err == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}),
|
||||||
|
WithServerRateLimit(10, 10, 10),
|
||||||
|
withAmplificationAttackPreventionDialWait(0),
|
||||||
|
)
|
||||||
|
defer an.Close()
|
||||||
|
defer an.host.Close()
|
||||||
|
|
||||||
|
c := newAutoNAT(t, nil, allowPrivateAddrs)
|
||||||
|
defer c.Close()
|
||||||
|
defer c.host.Close()
|
||||||
|
|
||||||
|
idAndWait(t, c, an)
|
||||||
|
|
||||||
|
var quicAddr, tcpAddr ma.Multiaddr
|
||||||
|
for _, a := range c.host.Addrs() {
|
||||||
|
if _, err := a.ValueForProtocol(ma.P_QUIC_V1); err == nil {
|
||||||
|
quicAddr = a
|
||||||
|
} else if _, err := a.ValueForProtocol(ma.P_TCP); err == nil {
|
||||||
|
tcpAddr = a
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := c.GetReachability(context.Background(), []Request{{Addr: tcpAddr, SendDialData: true}, {Addr: quicAddr}})
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
res, err := c.GetReachability(context.Background(), []Request{{Addr: quicAddr, SendDialData: true}, {Addr: tcpAddr}})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, Result{
|
||||||
|
Addr: quicAddr,
|
||||||
|
Reachability: network.ReachabilityPublic,
|
||||||
|
Status: pb.DialStatus_OK,
|
||||||
|
}, res)
|
||||||
|
|
||||||
|
// Small messages should be rejected for dial data
|
||||||
|
c.cli.dialData = c.cli.dialData[:10]
|
||||||
|
_, err = c.GetReachability(context.Background(), []Request{{Addr: quicAddr, SendDialData: true}, {Addr: tcpAddr}})
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
func TestServerDataRequestJitter(t *testing.T) {
|
||||||
|
// server will skip all tcp addresses
|
||||||
|
dialer := bhost.NewBlankHost(swarmt.GenSwarm(t, swarmt.OptDisableTCP))
|
||||||
|
// ask for dial data for quic address
|
||||||
|
an := newAutoNAT(t, dialer, allowPrivateAddrs, withDataRequestPolicy(
|
||||||
|
func(s network.Stream, dialAddr ma.Multiaddr) bool {
|
||||||
|
if _, err := dialAddr.ValueForProtocol(ma.P_QUIC_V1); err == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}),
|
||||||
|
WithServerRateLimit(10, 10, 10),
|
||||||
|
withAmplificationAttackPreventionDialWait(5*time.Second),
|
||||||
|
)
|
||||||
|
defer an.Close()
|
||||||
|
defer an.host.Close()
|
||||||
|
|
||||||
|
c := newAutoNAT(t, nil, allowPrivateAddrs)
|
||||||
|
defer c.Close()
|
||||||
|
defer c.host.Close()
|
||||||
|
|
||||||
|
idAndWait(t, c, an)
|
||||||
|
|
||||||
|
var quicAddr, tcpAddr ma.Multiaddr
|
||||||
|
for _, a := range c.host.Addrs() {
|
||||||
|
if _, err := a.ValueForProtocol(ma.P_QUIC_V1); err == nil {
|
||||||
|
quicAddr = a
|
||||||
|
} else if _, err := a.ValueForProtocol(ma.P_TCP); err == nil {
|
||||||
|
tcpAddr = a
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
st := time.Now()
|
||||||
|
res, err := c.GetReachability(context.Background(), []Request{{Addr: quicAddr, SendDialData: true}, {Addr: tcpAddr}})
|
||||||
|
took := time.Since(st)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, Result{
|
||||||
|
Addr: quicAddr,
|
||||||
|
Reachability: network.ReachabilityPublic,
|
||||||
|
Status: pb.DialStatus_OK,
|
||||||
|
}, res)
|
||||||
|
if took > 500*time.Millisecond {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.Fatalf("expected server to delay at least 1 dial")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServerDial(t *testing.T) {
|
||||||
|
an := newAutoNAT(t, nil, WithServerRateLimit(10, 10, 10), allowPrivateAddrs)
|
||||||
|
defer an.Close()
|
||||||
|
defer an.host.Close()
|
||||||
|
|
||||||
|
c := newAutoNAT(t, nil, allowPrivateAddrs)
|
||||||
|
defer c.Close()
|
||||||
|
defer c.host.Close()
|
||||||
|
|
||||||
|
idAndWait(t, c, an)
|
||||||
|
|
||||||
|
unreachableAddr := ma.StringCast("/ip4/1.2.3.4/tcp/2")
|
||||||
|
hostAddrs := c.host.Addrs()
|
||||||
|
|
||||||
|
t.Run("unreachable addr", func(t *testing.T) {
|
||||||
|
res, err := c.GetReachability(context.Background(),
|
||||||
|
append([]Request{{Addr: unreachableAddr, SendDialData: true}}, newTestRequests(hostAddrs, false)...))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, Result{
|
||||||
|
Addr: unreachableAddr,
|
||||||
|
Reachability: network.ReachabilityPrivate,
|
||||||
|
Status: pb.DialStatus_E_DIAL_ERROR,
|
||||||
|
}, res)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("reachable addr", func(t *testing.T) {
|
||||||
|
res, err := c.GetReachability(context.Background(), newTestRequests(c.host.Addrs(), false))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, Result{
|
||||||
|
Addr: hostAddrs[0],
|
||||||
|
Reachability: network.ReachabilityPublic,
|
||||||
|
Status: pb.DialStatus_OK,
|
||||||
|
}, res)
|
||||||
|
for _, addr := range c.host.Addrs() {
|
||||||
|
res, err := c.GetReachability(context.Background(), newTestRequests([]ma.Multiaddr{addr}, false))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, Result{
|
||||||
|
Addr: addr,
|
||||||
|
Reachability: network.ReachabilityPublic,
|
||||||
|
Status: pb.DialStatus_OK,
|
||||||
|
}, res)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("dialback error", func(t *testing.T) {
|
||||||
|
c.host.RemoveStreamHandler(DialBackProtocol)
|
||||||
|
res, err := c.GetReachability(context.Background(), newTestRequests(c.host.Addrs(), false))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, Result{
|
||||||
|
Addr: hostAddrs[0],
|
||||||
|
Reachability: network.ReachabilityUnknown,
|
||||||
|
Status: pb.DialStatus_E_DIAL_BACK_ERROR,
|
||||||
|
}, res)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRateLimiter(t *testing.T) {
|
||||||
|
cl := test.NewMockClock()
|
||||||
|
r := rateLimiter{RPM: 3, PerPeerRPM: 2, DialDataRPM: 1, now: cl.Now}
|
||||||
|
|
||||||
|
require.True(t, r.Accept("peer1"))
|
||||||
|
|
||||||
|
cl.AdvanceBy(10 * time.Second)
|
||||||
|
require.False(t, r.Accept("peer1")) // first request is still active
|
||||||
|
r.CompleteRequest("peer1")
|
||||||
|
|
||||||
|
require.True(t, r.Accept("peer1"))
|
||||||
|
r.CompleteRequest("peer1")
|
||||||
|
|
||||||
|
cl.AdvanceBy(10 * time.Second)
|
||||||
|
require.False(t, r.Accept("peer1"))
|
||||||
|
|
||||||
|
cl.AdvanceBy(10 * time.Second)
|
||||||
|
require.True(t, r.Accept("peer2"))
|
||||||
|
r.CompleteRequest("peer2")
|
||||||
|
|
||||||
|
cl.AdvanceBy(10 * time.Second)
|
||||||
|
require.False(t, r.Accept("peer3"))
|
||||||
|
|
||||||
|
cl.AdvanceBy(21 * time.Second) // first request expired
|
||||||
|
require.True(t, r.Accept("peer1"))
|
||||||
|
r.CompleteRequest("peer1")
|
||||||
|
|
||||||
|
cl.AdvanceBy(10 * time.Second)
|
||||||
|
require.True(t, r.Accept("peer3"))
|
||||||
|
r.CompleteRequest("peer3")
|
||||||
|
|
||||||
|
cl.AdvanceBy(50 * time.Second)
|
||||||
|
require.True(t, r.Accept("peer3"))
|
||||||
|
r.CompleteRequest("peer3")
|
||||||
|
|
||||||
|
cl.AdvanceBy(1 * time.Second)
|
||||||
|
require.False(t, r.Accept("peer3"))
|
||||||
|
|
||||||
|
cl.AdvanceBy(10 * time.Second)
|
||||||
|
require.True(t, r.Accept("peer3"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRateLimiterStress(t *testing.T) {
|
||||||
|
cl := test.NewMockClock()
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
r := rateLimiter{RPM: 20 + i, PerPeerRPM: 10 + i, DialDataRPM: i, now: cl.Now}
|
||||||
|
|
||||||
|
peers := make([]peer.ID, 10+i)
|
||||||
|
for i := 0; i < len(peers); i++ {
|
||||||
|
peers[i] = peer.ID(fmt.Sprintf("peer-%d", i))
|
||||||
|
}
|
||||||
|
peerSuccesses := make([]atomic.Int64, len(peers))
|
||||||
|
var success, dialDataSuccesses atomic.Int64
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
for k := 0; k < 5; k++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
for i := 0; i < 2*60; i++ {
|
||||||
|
for j, p := range peers {
|
||||||
|
if r.Accept(p) {
|
||||||
|
success.Add(1)
|
||||||
|
peerSuccesses[j].Add(1)
|
||||||
|
}
|
||||||
|
if r.AcceptDialDataRequest(p) {
|
||||||
|
dialDataSuccesses.Add(1)
|
||||||
|
}
|
||||||
|
r.CompleteRequest(p)
|
||||||
|
}
|
||||||
|
cl.AdvanceBy(time.Second)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
if int(success.Load()) > 10*r.RPM || int(success.Load()) < 9*r.RPM {
|
||||||
|
t.Fatalf("invalid successes, %d, expected %d-%d", success.Load(), 9*r.RPM, 10*r.RPM)
|
||||||
|
}
|
||||||
|
if int(dialDataSuccesses.Load()) > 10*r.DialDataRPM || int(dialDataSuccesses.Load()) < 9*r.DialDataRPM {
|
||||||
|
t.Fatalf("invalid dial data successes, %d expected %d-%d", dialDataSuccesses.Load(), 9*r.DialDataRPM, 10*r.DialDataRPM)
|
||||||
|
}
|
||||||
|
for i := range peerSuccesses {
|
||||||
|
// We cannot check the lower bound because some peers would be hitting the global rpm limit
|
||||||
|
if int(peerSuccesses[i].Load()) > 10*r.PerPeerRPM {
|
||||||
|
t.Fatalf("too many per peer successes, PerPeerRPM=%d", r.PerPeerRPM)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cl.AdvanceBy(1 * time.Minute)
|
||||||
|
require.True(t, r.Accept(peers[0]))
|
||||||
|
// Assert lengths to check that we are cleaning up correctly
|
||||||
|
require.Equal(t, len(r.reqs), 1)
|
||||||
|
require.Equal(t, len(r.peerReqs), 1)
|
||||||
|
require.Equal(t, len(r.peerReqs[peers[0]]), 1)
|
||||||
|
require.Equal(t, len(r.dialDataReqs), 0)
|
||||||
|
require.Equal(t, len(r.ongoingReqs), 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReadDialData(t *testing.T) {
|
||||||
|
for N := 30_000; N < 30_010; N++ {
|
||||||
|
for msgSize := 100; msgSize < 256; msgSize++ {
|
||||||
|
r, w := io.Pipe()
|
||||||
|
msg := &pb.Message{}
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
mw := pbio.NewDelimitedWriter(w)
|
||||||
|
err := sendDialData(make([]byte, msgSize), N, mw, msg)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
mw.Close()
|
||||||
|
}()
|
||||||
|
err := readDialData(N, r)
|
||||||
|
require.NoError(t, err)
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
for msgSize := 1000; msgSize < 1256; msgSize++ {
|
||||||
|
r, w := io.Pipe()
|
||||||
|
msg := &pb.Message{}
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
mw := pbio.NewDelimitedWriter(w)
|
||||||
|
err := sendDialData(make([]byte, msgSize), N, mw, msg)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
mw.Close()
|
||||||
|
}()
|
||||||
|
err := readDialData(N, r)
|
||||||
|
require.NoError(t, err)
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func FuzzServerDialRequest(f *testing.F) {
|
||||||
|
a := newAutoNAT(f, nil, allowPrivateAddrs, WithServerRateLimit(math.MaxInt32, math.MaxInt32, math.MaxInt32))
|
||||||
|
c := newAutoNAT(f, nil)
|
||||||
|
idAndWait(f, c, a)
|
||||||
|
// reduce the streamTimeout before running this. TODO: fix this
|
||||||
|
f.Fuzz(func(t *testing.T, data []byte) {
|
||||||
|
s, err := c.host.NewStream(context.Background(), a.host.ID(), DialProtocol)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
s.SetDeadline(time.Now().Add(10 * time.Second))
|
||||||
|
s.Write(data)
|
||||||
|
buf := make([]byte, 64)
|
||||||
|
s.Read(buf) // We only care that server didn't panic
|
||||||
|
s, err = c.host.NewStream(context.Background(), a.host.ID(), DialProtocol)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
n := varint.PutUvarint(buf, uint64(len(data)))
|
||||||
|
s.SetDeadline(time.Now().Add(10 * time.Second))
|
||||||
|
s.Write(buf[:n])
|
||||||
|
s.Write(data)
|
||||||
|
s.Read(buf) // We only care that server didn't panic
|
||||||
|
s.Reset()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func FuzzReadDialData(f *testing.F) {
|
||||||
|
f.Fuzz(func(t *testing.T, numBytes int, data []byte) {
|
||||||
|
readDialData(numBytes, bytes.NewReader(data))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkDialData(b *testing.B) {
|
||||||
|
b.ReportAllocs()
|
||||||
|
const N = 100_000
|
||||||
|
streamBuffer := make([]byte, 2*N)
|
||||||
|
buf := bytes.NewBuffer(streamBuffer[:0])
|
||||||
|
dialData := make([]byte, 4000)
|
||||||
|
msg := &pb.Message{}
|
||||||
|
w := pbio.NewDelimitedWriter(buf)
|
||||||
|
err := sendDialData(dialData, N, w, msg)
|
||||||
|
require.NoError(b, err)
|
||||||
|
dialDataBuf := buf.Bytes()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
err = readDialData(N, bytes.NewReader(dialDataBuf))
|
||||||
|
require.NoError(b, err)
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user