diff --git a/defaults.go b/defaults.go index 31de6f025d..7d6e18a114 100644 --- a/defaults.go +++ b/defaults.go @@ -1,3 +1,6 @@ +//go:build !js +// +build !js + package libp2p // This file contains all the default configuration options. diff --git a/defaults_js.go b/defaults_js.go new file mode 100644 index 0000000000..c76ca490b6 --- /dev/null +++ b/defaults_js.go @@ -0,0 +1,188 @@ +//go:build js +// +build js + +package libp2p + +import ( + "crypto/rand" + + "github.com/libp2p/go-libp2p/core/crypto" + "github.com/libp2p/go-libp2p/p2p/host/peerstore/pstoremem" + rcmgr "github.com/libp2p/go-libp2p/p2p/host/resource-manager" + "github.com/libp2p/go-libp2p/p2p/muxer/yamux" + "github.com/libp2p/go-libp2p/p2p/net/connmgr" + "github.com/libp2p/go-libp2p/p2p/net/swarm" + "github.com/libp2p/go-libp2p/p2p/security/noise" + "github.com/prometheus/client_golang/prometheus" +) + +var DefaultSecurity = ChainOptions( + Security(noise.ID, noise.New), +) + +var DefaultMuxers = Muxer(yamux.ID, yamux.DefaultTransport) + +var DefaultTransports = ChainOptions(NoTransports) + +// DefaultPrivateTransports are the default libp2p transports when a PSK is supplied. +// +// Use this option when you want to *extend* the set of transports used by +// libp2p instead of replacing them. +var DefaultPrivateTransports = ChainOptions(NoTransports) + +// DefaultPeerstore configures libp2p to use the default peerstore. +var DefaultPeerstore Option = func(cfg *Config) error { + ps, err := pstoremem.NewPeerstore() + if err != nil { + return err + } + return cfg.Apply(Peerstore(ps)) +} + +// RandomIdentity generates a random identity. (default behaviour) +var RandomIdentity = func(cfg *Config) error { + priv, _, err := crypto.GenerateEd25519Key(rand.Reader) + if err != nil { + return err + } + return cfg.Apply(Identity(priv)) +} + +// DefaultListenAddrs configures libp2p to use default listen address. +var DefaultListenAddrs = NoListenAddrs + +// DefaultEnableRelay enables relay dialing and listening by default. +var DefaultEnableRelay = func(cfg *Config) error { + return cfg.Apply(EnableRelay()) +} + +var DefaultResourceManager = func(cfg *Config) error { + // Default memory limit: 1/8th of total memory, minimum 128MB, maximum 1GB + limits := rcmgr.DefaultLimits + SetDefaultServiceLimits(&limits) + mgr, err := rcmgr.NewResourceManager(rcmgr.NewFixedLimiter(limits.AutoScale())) + if err != nil { + return err + } + + return cfg.Apply(ResourceManager(mgr)) +} + +// DefaultConnectionManager creates a default connection manager +var DefaultConnectionManager = func(cfg *Config) error { + mgr, err := connmgr.NewConnManager(160, 192) + if err != nil { + return err + } + + return cfg.Apply(ConnectionManager(mgr)) +} + +// DefaultPrometheusRegisterer configures libp2p to use the default registerer +var DefaultPrometheusRegisterer = func(cfg *Config) error { + return cfg.Apply(PrometheusRegisterer(prometheus.DefaultRegisterer)) +} + +var defaultUDPBlackHoleDetector = func(cfg *Config) error { + // A black hole is a binary property. On a network if UDP dials are blocked, all dials will + // fail. So a low success rate of 5 out 100 dials is good enough. + return cfg.Apply(UDPBlackHoleSuccessCounter(&swarm.BlackHoleSuccessCounter{N: 100, MinSuccesses: 5, Name: "UDP"})) +} + +var defaultIPv6BlackHoleDetector = func(cfg *Config) error { + // A black hole is a binary property. On a network if there is no IPv6 connectivity, all + // dials will fail. So a low success rate of 5 out 100 dials is good enough. + return cfg.Apply(IPv6BlackHoleSuccessCounter(&swarm.BlackHoleSuccessCounter{N: 100, MinSuccesses: 5, Name: "IPv6"})) +} + +// Complete list of default options and when to fallback on them. +// +// Please *DON'T* specify default options any other way. Putting this all here +// makes tracking defaults *much* easier. +var defaults = []struct { + fallback func(cfg *Config) bool + opt Option +}{ + { + fallback: func(cfg *Config) bool { return cfg.Transports == nil && cfg.ListenAddrs == nil }, + opt: DefaultListenAddrs, + }, + { + fallback: func(cfg *Config) bool { return cfg.Transports == nil && cfg.PSK == nil }, + opt: DefaultTransports, + }, + { + fallback: func(cfg *Config) bool { return cfg.Transports == nil && cfg.PSK != nil }, + opt: DefaultPrivateTransports, + }, + { + fallback: func(cfg *Config) bool { return cfg.Muxers == nil }, + opt: DefaultMuxers, + }, + { + fallback: func(cfg *Config) bool { return !cfg.Insecure && cfg.SecurityTransports == nil }, + opt: DefaultSecurity, + }, + { + fallback: func(cfg *Config) bool { return cfg.PeerKey == nil }, + opt: RandomIdentity, + }, + { + fallback: func(cfg *Config) bool { return cfg.Peerstore == nil }, + opt: DefaultPeerstore, + }, + { + fallback: func(cfg *Config) bool { return !cfg.RelayCustom }, + opt: DefaultEnableRelay, + }, + { + fallback: func(cfg *Config) bool { return cfg.ResourceManager == nil }, + opt: DefaultResourceManager, + }, + { + fallback: func(cfg *Config) bool { return cfg.ConnManager == nil }, + opt: DefaultConnectionManager, + }, + { + fallback: func(cfg *Config) bool { return !cfg.DisableMetrics && cfg.PrometheusRegisterer == nil }, + opt: DefaultPrometheusRegisterer, + }, + { + fallback: func(cfg *Config) bool { + return !cfg.CustomUDPBlackHoleSuccessCounter && cfg.UDPBlackHoleSuccessCounter == nil + }, + opt: defaultUDPBlackHoleDetector, + }, + { + fallback: func(cfg *Config) bool { + return !cfg.CustomIPv6BlackHoleSuccessCounter && cfg.IPv6BlackHoleSuccessCounter == nil + }, + opt: defaultIPv6BlackHoleDetector, + }, +} + +// Defaults configures libp2p to use the default options. Can be combined with +// other options to *extend* the default options. +var Defaults Option = func(cfg *Config) error { + for _, def := range defaults { + if err := cfg.Apply(def.opt); err != nil { + return err + } + } + return nil +} + +// FallbackDefaults applies default options to the libp2p node if and only if no +// other relevant options have been applied. will be appended to the options +// passed into New. +var FallbackDefaults Option = func(cfg *Config) error { + for _, def := range defaults { + if !def.fallback(cfg) { + continue + } + if err := cfg.Apply(def.opt); err != nil { + return err + } + } + return nil +} diff --git a/examples/go.mod b/examples/go.mod index 3bb1c16970..b997f73066 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -1,18 +1,21 @@ module github.com/libp2p/go-libp2p/examples -go 1.23.0 +go 1.23.8 + +toolchain go1.24.5 require ( github.com/caddyserver/certmagic v0.21.6 github.com/gogo/protobuf v1.3.2 github.com/google/uuid v1.6.0 - github.com/ipfs/go-datastore v0.6.0 - github.com/ipfs/go-log/v2 v2.5.1 + github.com/ipfs/go-datastore v0.8.2 + github.com/ipfs/go-log/v2 v2.6.0 github.com/ipshipyard/p2p-forge v0.5.0 github.com/libp2p/go-libp2p v0.41.1 github.com/libp2p/go-libp2p-kad-dht v0.28.1 - github.com/multiformats/go-multiaddr v0.15.0 - github.com/prometheus/client_golang v1.21.1 + github.com/multiformats/go-multiaddr v0.16.0 + github.com/prometheus/client_golang v1.22.0 + go.uber.org/fx v1.24.0 ) require ( @@ -20,22 +23,15 @@ require ( github.com/beorn7/perks v1.0.1 // indirect github.com/caddyserver/zerossl v0.1.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/containerd/cgroups v1.1.0 // indirect - github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect - github.com/docker/go-units v0.5.0 // indirect - github.com/elastic/gosigar v0.14.3 // indirect github.com/flynn/noise v1.1.0 // indirect github.com/francoispqt/gojay v1.2.13 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-task/slim-sprig/v3 v3.0.0 // indirect - github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/google/gopacket v1.1.19 // indirect - github.com/google/pprof v0.0.0-20250208200701-d0013a598941 // indirect github.com/gorilla/websocket v1.5.3 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect @@ -46,10 +42,9 @@ require ( github.com/ipld/go-ipld-prime v0.21.0 // indirect github.com/jackpal/go-nat-pmp v1.0.2 // indirect github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect - github.com/jbenet/goprocess v0.1.4 // indirect github.com/klauspost/compress v1.18.0 // indirect github.com/klauspost/cpuid/v2 v2.2.10 // indirect - github.com/koron/go-ssdp v0.0.5 // indirect + github.com/koron/go-ssdp v0.0.6 // indirect github.com/libdns/libdns v0.2.2 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect github.com/libp2p/go-cidranger v1.1.0 // indirect @@ -61,12 +56,12 @@ require ( github.com/libp2p/go-msgio v0.3.0 // indirect github.com/libp2p/go-netroute v0.2.2 // indirect github.com/libp2p/go-reuseport v0.4.0 // indirect - github.com/libp2p/go-yamux/v5 v5.0.0 // indirect + github.com/libp2p/go-yamux/v5 v5.0.1 // indirect github.com/libp2p/zeroconf/v2 v2.2.0 // indirect github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mholt/acmez/v3 v3.0.0 // indirect - github.com/miekg/dns v1.1.64 // indirect + github.com/miekg/dns v1.1.66 // indirect github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect github.com/minio/sha256-simd v1.0.1 // indirect @@ -76,67 +71,64 @@ require ( github.com/multiformats/go-multiaddr-dns v0.4.1 // indirect github.com/multiformats/go-multiaddr-fmt v0.1.0 // indirect github.com/multiformats/go-multibase v0.2.0 // indirect - github.com/multiformats/go-multicodec v0.9.0 // indirect + github.com/multiformats/go-multicodec v0.9.1 // indirect github.com/multiformats/go-multihash v0.2.3 // indirect - github.com/multiformats/go-multistream v0.6.0 // indirect + github.com/multiformats/go-multistream v0.6.1 // indirect github.com/multiformats/go-varint v0.0.7 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/onsi/ginkgo/v2 v2.22.2 // indirect - github.com/opencontainers/runtime-spec v1.2.0 // indirect github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect github.com/pion/datachannel v1.5.10 // indirect github.com/pion/dtls/v2 v2.2.12 // indirect - github.com/pion/dtls/v3 v3.0.4 // indirect - github.com/pion/ice/v4 v4.0.8 // indirect - github.com/pion/interceptor v0.1.37 // indirect + github.com/pion/dtls/v3 v3.0.6 // indirect + github.com/pion/ice/v4 v4.0.10 // indirect + github.com/pion/interceptor v0.1.40 // indirect github.com/pion/logging v0.2.3 // indirect github.com/pion/mdns/v2 v2.0.7 // indirect github.com/pion/randutil v0.1.0 // indirect github.com/pion/rtcp v1.2.15 // indirect - github.com/pion/rtp v1.8.11 // indirect - github.com/pion/sctp v1.8.37 // indirect - github.com/pion/sdp/v3 v3.0.10 // indirect - github.com/pion/srtp/v3 v3.0.4 // indirect + github.com/pion/rtp v1.8.19 // indirect + github.com/pion/sctp v1.8.39 // indirect + github.com/pion/sdp/v3 v3.0.13 // indirect + github.com/pion/srtp/v3 v3.0.6 // indirect github.com/pion/stun v0.6.1 // indirect github.com/pion/stun/v3 v3.0.0 // indirect github.com/pion/transport/v2 v2.2.10 // indirect github.com/pion/transport/v3 v3.0.7 // indirect - github.com/pion/turn/v4 v4.0.0 // indirect - github.com/pion/webrtc/v4 v4.0.10 // indirect - github.com/pkg/errors v0.9.1 // indirect + github.com/pion/turn/v4 v4.0.2 // indirect + github.com/pion/webrtc/v4 v4.1.2 // indirect github.com/polydawn/refmt v0.89.0 // indirect - github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.62.0 // indirect - github.com/prometheus/procfs v0.15.1 // indirect + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.64.0 // indirect + github.com/prometheus/procfs v0.16.1 // indirect github.com/quic-go/qpack v0.5.1 // indirect - github.com/quic-go/quic-go v0.50.1 // indirect - github.com/quic-go/webtransport-go v0.8.1-0.20241018022711-4ac2c9250e66 // indirect - github.com/raulk/go-watchdog v1.3.0 // indirect + github.com/quic-go/quic-go v0.53.0 // indirect + github.com/quic-go/webtransport-go v0.9.0 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 // indirect github.com/wlynxg/anet v0.0.5 // indirect github.com/zeebo/blake3 v0.2.4 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/otel v1.33.0 // indirect - go.opentelemetry.io/otel/metric v1.33.0 // indirect - go.opentelemetry.io/otel/trace v1.33.0 // indirect - go.uber.org/dig v1.18.0 // indirect - go.uber.org/fx v1.23.0 // indirect - go.uber.org/mock v0.5.0 // indirect + go.opentelemetry.io/otel v1.34.0 // indirect + go.opentelemetry.io/otel/metric v1.34.0 // indirect + go.opentelemetry.io/otel/trace v1.34.0 // indirect + go.uber.org/dig v1.19.0 // indirect + go.uber.org/mock v0.5.2 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect go.uber.org/zap/exp v0.3.0 // indirect - golang.org/x/crypto v0.35.0 // indirect - golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa // indirect - golang.org/x/mod v0.23.0 // indirect - golang.org/x/net v0.35.0 // indirect - golang.org/x/sync v0.11.0 // indirect - golang.org/x/sys v0.30.0 // indirect - golang.org/x/text v0.22.0 // indirect - golang.org/x/time v0.8.0 // indirect - golang.org/x/tools v0.30.0 // indirect + golang.org/x/crypto v0.39.0 // indirect + golang.org/x/exp v0.0.0-20250606033433-dcc06ee1d476 // indirect + golang.org/x/mod v0.25.0 // indirect + golang.org/x/net v0.41.0 // indirect + golang.org/x/sync v0.15.0 // indirect + golang.org/x/sys v0.33.0 // indirect + golang.org/x/text v0.26.0 // indirect + golang.org/x/time v0.12.0 // indirect + golang.org/x/tools v0.34.0 // indirect gonum.org/v1/gonum v0.15.1 // indirect google.golang.org/protobuf v1.36.6 // indirect - lukechampine.com/blake3 v1.4.0 // indirect + lukechampine.com/blake3 v1.4.1 // indirect ) + +replace github.com/libp2p/go-libp2p => ../ diff --git a/examples/go.sum b/examples/go.sum index 057983ce62..250f79708f 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -9,8 +9,6 @@ dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= -github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= -github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -25,18 +23,10 @@ github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/X github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/containerd/cgroups v0.0.0-20201119153540-4cbc285b3327/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= -github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= -github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw= github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= -github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= -github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -47,13 +37,7 @@ github.com/decred/dcrd/crypto/blake256 v1.1.0 h1:zPMNGQCm0g4QTY27fOCorQW7EryeQ/U github.com/decred/dcrd/crypto/blake256 v1.1.0/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= -github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= -github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/elastic/gosigar v0.12.0/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs= -github.com/elastic/gosigar v0.14.3 h1:xwkKwPia+hSfg9GqrCUKYdId102m9qTJIIr7egmK/uo= -github.com/elastic/gosigar v0.14.3/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -74,15 +58,8 @@ github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= -github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0= -github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= -github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -108,18 +85,15 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20250208200701-d0013a598941 h1:43XjGa6toxLpeksjcxs1jIoIyr+vUfOqY2c6HB4bpoc= -github.com/google/pprof v0.0.0-20250208200701-d0013a598941/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -147,14 +121,14 @@ github.com/ipfs/go-block-format v0.2.0 h1:ZqrkxBA2ICbDRbK8KJs/u0O3dlp6gmAuuXUJNi github.com/ipfs/go-block-format v0.2.0/go.mod h1:+jpL11nFx5A/SPpsoBn6Bzkra/zaArfSmsknbPMYgzM= github.com/ipfs/go-cid v0.5.0 h1:goEKKhaGm0ul11IHA7I6p1GmKz8kEYniqFopaB5Otwg= github.com/ipfs/go-cid v0.5.0/go.mod h1:0L7vmeNXpQpUS9vt+yEARkJ8rOg43DF3iPgn4GIN0mk= -github.com/ipfs/go-datastore v0.6.0 h1:JKyz+Gvz1QEZw0LsX1IBn+JFCJQH4SJVFtM4uWU0Myk= -github.com/ipfs/go-datastore v0.6.0/go.mod h1:rt5M3nNbSO/8q1t4LNkLyUwRs8HupMeN/8O4Vn9YAT8= +github.com/ipfs/go-datastore v0.8.2 h1:Jy3wjqQR6sg/LhyY0NIePZC3Vux19nLtg7dx0TVqr6U= +github.com/ipfs/go-datastore v0.8.2/go.mod h1:W+pI1NsUsz3tcsAACMtfC+IZdnQTnC/7VfPoJBQuts0= github.com/ipfs/go-detect-race v0.0.1 h1:qX/xay2W3E4Q1U7d9lNs1sU9nvguX0a7319XbyQ6cOk= github.com/ipfs/go-detect-race v0.0.1/go.mod h1:8BNT7shDZPo99Q74BpGMK+4D8Mn4j46UU0LZ723meps= github.com/ipfs/go-ipfs-util v0.0.3 h1:2RFdGez6bu2ZlZdI+rWfIdbQb1KudQp3VGwPtdNCmE0= github.com/ipfs/go-ipfs-util v0.0.3/go.mod h1:LHzG1a0Ig4G+iZ26UUOMjHd+lfM84LZCrn17xAKWBvs= -github.com/ipfs/go-log/v2 v2.5.1 h1:1XdUzF7048prq4aBjDQQ4SL5RxftpRGdXhNRwKSAlcY= -github.com/ipfs/go-log/v2 v2.5.1/go.mod h1:prSpmC1Gpllc9UYWxDiZDreBYw7zp4Iqp1kOLU9U5UI= +github.com/ipfs/go-log/v2 v2.6.0 h1:2Nu1KKQQ2ayonKp4MPo6pXCjqw1ULc9iohRqWV5EYqg= +github.com/ipfs/go-log/v2 v2.6.0/go.mod h1:p+Efr3qaY5YXpx9TX7MoLCSEZX5boSWj9wh86P5HJa8= github.com/ipfs/go-test v0.0.4 h1:DKT66T6GBB6PsDFLoO56QZPrOmzJkqU1FZH5C9ySkew= github.com/ipfs/go-test v0.0.4/go.mod h1:qhIM1EluEfElKKM6fnWxGn822/z9knUGM1+I/OAQNKI= github.com/ipld/go-ipld-prime v0.21.0 h1:n4JmcpOlPDIxBcY037SVfpd1G+Sj1nKZah0m6QH9C2E= @@ -163,25 +137,21 @@ github.com/ipshipyard/p2p-forge v0.5.0 h1:U1ta2RYkSOLPXNbeCWGT5iv5t5TS1GNDvE1hSu github.com/ipshipyard/p2p-forge v0.5.0/go.mod h1:GNDXM2CR8KRS8mJGw7ARIRVlrG9NH8MdewgNVfIIByA= github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= -github.com/jbenet/go-cienv v0.1.0/go.mod h1:TqNnHUmJgXau0nCzC7kXWeotg3J9W34CUv5Djy1+FlA= github.com/jbenet/go-temp-err-catcher v0.1.0 h1:zpb3ZH6wIE8Shj2sKS+khgRvf7T7RABoLk/+KKHggpk= github.com/jbenet/go-temp-err-catcher v0.1.0/go.mod h1:0kJRvmDZXNMIiJirNPEYfhpPwbGVtZVWC34vc5WLsDk= -github.com/jbenet/goprocess v0.1.4 h1:DRGOFReOMqqDNXwW70QkacFW0YN9QnwLV0Vqk+3oU0o= -github.com/jbenet/goprocess v0.1.4/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZlqdZVfqY4= github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= -github.com/koron/go-ssdp v0.0.5 h1:E1iSMxIs4WqxTbIBLtmNBeOOC+1sCIXQeqTWVnpmwhk= -github.com/koron/go-ssdp v0.0.5/go.mod h1:Qm59B7hpKpDqfyRNWRNr00jGwLdXjDyZh6y7rH6VS0w= +github.com/koron/go-ssdp v0.0.6 h1:Jb0h04599eq/CY7rB5YEqPS83HmRfHP2azkxMN2rFtU= +github.com/koron/go-ssdp v0.0.6/go.mod h1:0R9LfRJGek1zWTjN3JUNlm5INCDYGpRDfAptnct63fI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -201,8 +171,6 @@ github.com/libp2p/go-cidranger v1.1.0 h1:ewPN8EZ0dd1LSnrtuwd4709PXVcITVeuwbag38y github.com/libp2p/go-cidranger v1.1.0/go.mod h1:KWZTfSr+r9qEo9OkI9/SIEeAtw+NNoU0dXIXt15Okic= github.com/libp2p/go-flow-metrics v0.2.0 h1:EIZzjmeOE6c8Dav0sNv35vhZxATIXWZg6j/C08XmmDw= github.com/libp2p/go-flow-metrics v0.2.0/go.mod h1:st3qqfu8+pMfh+9Mzqb2GTiwrAGjIPszEjZmtksN8Jc= -github.com/libp2p/go-libp2p v0.41.1 h1:8ecNQVT5ev/jqALTvisSJeVNvXYJyK4NhQx1nNRXQZE= -github.com/libp2p/go-libp2p v0.41.1/go.mod h1:DcGTovJzQl/I7HMrby5ZRjeD0kQkGiy+9w6aEkSZpRI= github.com/libp2p/go-libp2p-asn-util v0.4.1 h1:xqL7++IKD9TBFMgnLPZR6/6iYhawHKHl950SO9L6n94= github.com/libp2p/go-libp2p-asn-util v0.4.1/go.mod h1:d/NI6XZ9qxw67b4e+NgpQexCIiFYJjErASrYW4PFDN8= github.com/libp2p/go-libp2p-kad-dht v0.28.1 h1:DVTfzG8Ybn88g9RycIq47evWCRss5f0Wm8iWtpwyHso= @@ -221,15 +189,14 @@ github.com/libp2p/go-netroute v0.2.2 h1:Dejd8cQ47Qx2kRABg6lPwknU7+nBnFRpko45/fFP github.com/libp2p/go-netroute v0.2.2/go.mod h1:Rntq6jUAH0l9Gg17w5bFGhcC9a+vk4KNXs6s7IljKYE= github.com/libp2p/go-reuseport v0.4.0 h1:nR5KU7hD0WxXCJbmw7r2rhRYruNRl2koHw8fQscQm2s= github.com/libp2p/go-reuseport v0.4.0/go.mod h1:ZtI03j/wO5hZVDFo2jKywN6bYKWLOy8Se6DrI2E1cLU= -github.com/libp2p/go-yamux/v5 v5.0.0 h1:2djUh96d3Jiac/JpGkKs4TO49YhsfLopAoryfPmf+Po= -github.com/libp2p/go-yamux/v5 v5.0.0/go.mod h1:en+3cdX51U0ZslwRdRLrvQsdayFt3TSUKvBGErzpWbU= +github.com/libp2p/go-yamux/v5 v5.0.1 h1:f0WoX/bEF2E8SbE4c/k1Mo+/9z0O4oC/hWEA+nfYRSg= +github.com/libp2p/go-yamux/v5 v5.0.1/go.mod h1:en+3cdX51U0ZslwRdRLrvQsdayFt3TSUKvBGErzpWbU= github.com/libp2p/zeroconf/v2 v2.2.0 h1:Cup06Jv6u81HLhIj1KasuNM/RHHrJ8T7wOTS4+Tv53Q= github.com/libp2p/zeroconf/v2 v2.2.0/go.mod h1:fuJqLnUwZTshS3U/bMRJ3+ow/v9oid1n0DmyYyNO1Xs= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd h1:br0buuQ854V8u83wA0rVZ8ttrq5CpaPZdvrK0LP2lOk= github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd/go.mod h1:QuCEs1Nt24+FYQEqAAncTDPJIuGs+LxK1MCiFL25pMU= -github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= @@ -237,8 +204,8 @@ github.com/mholt/acmez/v3 v3.0.0 h1:r1NcjuWR0VaKP2BTjDK9LRFBw/WvURx3jlaEUl9Ht8E= github.com/mholt/acmez/v3 v3.0.0/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ= github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= -github.com/miekg/dns v1.1.64 h1:wuZgD9wwCE6XMT05UU/mlSko71eRSXEAm2EbjQXLKnQ= -github.com/miekg/dns v1.1.64/go.mod h1:Dzw9769uoKVaLuODMDZz9M6ynFU6Em65csPuoi8G0ck= +github.com/miekg/dns v1.1.66 h1:FeZXOS3VCVsKnEAd+wBkjMC3D2K+ww66Cq3VnCINuJE= +github.com/miekg/dns v1.1.66/go.mod h1:jGFzBsSNbJw6z1HYut1RKBKHA9PBdxeHrZG8J+gC2WE= github.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c h1:bzE/A84HN25pxAuk9Eej1Kz9OUelF97nAc82bDquQI8= github.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c/go.mod h1:0SQS9kMwD2VsyFEB++InYyBJroV/FRmBgcydeSUcJms= github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b h1:z78hV3sbSMAUoyUMM0I83AUIT6Hu17AWfgjzIbtrYFc= @@ -259,34 +226,27 @@ github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYg github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0= github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4= github.com/multiformats/go-multiaddr v0.1.1/go.mod h1:aMKBKNEYmzmDmxfX88/vz+J5IU55txyt0p4aiWVohjo= -github.com/multiformats/go-multiaddr v0.15.0 h1:zB/HeaI/apcZiTDwhY5YqMvNVl/oQYvs3XySU+qeAVo= -github.com/multiformats/go-multiaddr v0.15.0/go.mod h1:JSVUmXDjsVFiW7RjIFMP7+Ev+h1DTbiJgVeTV/tcmP0= +github.com/multiformats/go-multiaddr v0.16.0 h1:oGWEVKioVQcdIOBlYM8BH1rZDWOGJSqr9/BKl6zQ4qc= +github.com/multiformats/go-multiaddr v0.16.0/go.mod h1:JSVUmXDjsVFiW7RjIFMP7+Ev+h1DTbiJgVeTV/tcmP0= github.com/multiformats/go-multiaddr-dns v0.4.1 h1:whi/uCLbDS3mSEUMb1MsoT4uzUeZB0N32yzufqS0i5M= github.com/multiformats/go-multiaddr-dns v0.4.1/go.mod h1:7hfthtB4E4pQwirrz+J0CcDUfbWzTqEzVyYKKIKpgkc= github.com/multiformats/go-multiaddr-fmt v0.1.0 h1:WLEFClPycPkp4fnIzoFoV9FVd49/eQsuaL3/CWe167E= github.com/multiformats/go-multiaddr-fmt v0.1.0/go.mod h1:hGtDIW4PU4BqJ50gW2quDuPVjyWNZxToGUh/HwTZYJo= github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g= github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk= -github.com/multiformats/go-multicodec v0.9.0 h1:pb/dlPnzee/Sxv/j4PmkDRxCOi3hXTz3IbPKOXWJkmg= -github.com/multiformats/go-multicodec v0.9.0/go.mod h1:L3QTQvMIaVBkXOXXtVmYE+LI16i14xuaojr/H7Ai54k= +github.com/multiformats/go-multicodec v0.9.1 h1:x/Fuxr7ZuR4jJV4Os5g444F7xC4XmyUaT/FWtE+9Zjo= +github.com/multiformats/go-multicodec v0.9.1/go.mod h1:LLWNMtyV5ithSBUo3vFIMaeDy+h3EbkMTek1m+Fybbo= github.com/multiformats/go-multihash v0.0.8/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew= github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U= github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM= -github.com/multiformats/go-multistream v0.6.0 h1:ZaHKbsL404720283o4c/IHQXiS6gb8qAN5EIJ4PN5EA= -github.com/multiformats/go-multistream v0.6.0/go.mod h1:MOyoG5otO24cHIg8kf9QW2/NozURlkP/rvi2FQJyCPg= +github.com/multiformats/go-multistream v0.6.1 h1:4aoX5v6T+yWmc2raBHsTvzmFhOI8WVOer28DeBBEYdQ= +github.com/multiformats/go-multistream v0.6.1/go.mod h1:ksQf6kqHAb6zIsyw7Zm+gAuVo57Qbq84E27YlYqavqw= github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8= github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= -github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU= -github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk= -github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= -github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= -github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-spec v1.2.0 h1:z97+pHb3uELt/yiAWD691HNHQIF07bE7dzrbT927iTk= -github.com/opencontainers/runtime-spec v1.2.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0= github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y= @@ -295,12 +255,12 @@ github.com/pion/datachannel v1.5.10/go.mod h1:p/jJfC9arb29W7WrxyKbepTU20CFgyx5oL github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s= github.com/pion/dtls/v2 v2.2.12 h1:KP7H5/c1EiVAAKUmXyCzPiQe5+bCJrpOeKg/L05dunk= github.com/pion/dtls/v2 v2.2.12/go.mod h1:d9SYc9fch0CqK90mRk1dC7AkzzpwJj6u2GU3u+9pqFE= -github.com/pion/dtls/v3 v3.0.4 h1:44CZekewMzfrn9pmGrj5BNnTMDCFwr+6sLH+cCuLM7U= -github.com/pion/dtls/v3 v3.0.4/go.mod h1:R373CsjxWqNPf6MEkfdy3aSe9niZvL/JaKlGeFphtMg= -github.com/pion/ice/v4 v4.0.8 h1:ajNx0idNG+S+v9Phu4LSn2cs8JEfTsA1/tEjkkAVpFY= -github.com/pion/ice/v4 v4.0.8/go.mod h1:y3M18aPhIxLlcO/4dn9X8LzLLSma84cx6emMSu14FGw= -github.com/pion/interceptor v0.1.37 h1:aRA8Zpab/wE7/c0O3fh1PqY0AJI3fCSEM5lRWJVorwI= -github.com/pion/interceptor v0.1.37/go.mod h1:JzxbJ4umVTlZAf+/utHzNesY8tmRkM2lVmkS82TTj8Y= +github.com/pion/dtls/v3 v3.0.6 h1:7Hkd8WhAJNbRgq9RgdNh1aaWlZlGpYTzdqjy9x9sK2E= +github.com/pion/dtls/v3 v3.0.6/go.mod h1:iJxNQ3Uhn1NZWOMWlLxEEHAN5yX7GyPvvKw04v9bzYU= +github.com/pion/ice/v4 v4.0.10 h1:P59w1iauC/wPk9PdY8Vjl4fOFL5B+USq1+xbDcN6gT4= +github.com/pion/ice/v4 v4.0.10/go.mod h1:y3M18aPhIxLlcO/4dn9X8LzLLSma84cx6emMSu14FGw= +github.com/pion/interceptor v0.1.40 h1:e0BjnPcGpr2CFQgKhrQisBU7V3GXK6wrfYrGYaU6Jq4= +github.com/pion/interceptor v0.1.40/go.mod h1:Z6kqH7M/FYirg3frjGJ21VLSRJGBXB/KqaTIrdqnOic= github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= github.com/pion/logging v0.2.3 h1:gHuf0zpoh1GW67Nr6Gj4cv5Z9ZscU7g/EaoC/Ke/igI= github.com/pion/logging v0.2.3/go.mod h1:z8YfknkquMe1csOrxK5kc+5/ZPAzMxbKLX5aXpbpC90= @@ -310,14 +270,14 @@ github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= github.com/pion/rtcp v1.2.15 h1:LZQi2JbdipLOj4eBjK4wlVoQWfrZbh3Q6eHtWtJBZBo= github.com/pion/rtcp v1.2.15/go.mod h1:jlGuAjHMEXwMUHK78RgX0UmEJFV4zUKOFHR7OP+D3D0= -github.com/pion/rtp v1.8.11 h1:17xjnY5WO5hgO6SD3/NTIUPvSFw/PbLsIJyz1r1yNIk= -github.com/pion/rtp v1.8.11/go.mod h1:8uMBJj32Pa1wwx8Fuv/AsFhn8jsgw+3rUC2PfoBZ8p4= -github.com/pion/sctp v1.8.37 h1:ZDmGPtRPX9mKCiVXtMbTWybFw3z/hVKAZgU81wcOrqs= -github.com/pion/sctp v1.8.37/go.mod h1:cNiLdchXra8fHQwmIoqw0MbLLMs+f7uQ+dGMG2gWebE= -github.com/pion/sdp/v3 v3.0.10 h1:6MChLE/1xYB+CjumMw+gZ9ufp2DPApuVSnDT8t5MIgA= -github.com/pion/sdp/v3 v3.0.10/go.mod h1:88GMahN5xnScv1hIMTqLdu/cOcUkj6a9ytbncwMCq2E= -github.com/pion/srtp/v3 v3.0.4 h1:2Z6vDVxzrX3UHEgrUyIGM4rRouoC7v+NiF1IHtp9B5M= -github.com/pion/srtp/v3 v3.0.4/go.mod h1:1Jx3FwDoxpRaTh1oRV8A/6G1BnFL+QI82eK4ms8EEJQ= +github.com/pion/rtp v1.8.19 h1:jhdO/3XhL/aKm/wARFVmvTfq0lC/CvN1xwYKmduly3c= +github.com/pion/rtp v1.8.19/go.mod h1:bAu2UFKScgzyFqvUKmbvzSdPr+NGbZtv6UB2hesqXBk= +github.com/pion/sctp v1.8.39 h1:PJma40vRHa3UTO3C4MyeJDQ+KIobVYRZQZ0Nt7SjQnE= +github.com/pion/sctp v1.8.39/go.mod h1:cNiLdchXra8fHQwmIoqw0MbLLMs+f7uQ+dGMG2gWebE= +github.com/pion/sdp/v3 v3.0.13 h1:uN3SS2b+QDZnWXgdr69SM8KB4EbcnPnPf2Laxhty/l4= +github.com/pion/sdp/v3 v3.0.13/go.mod h1:88GMahN5xnScv1hIMTqLdu/cOcUkj6a9ytbncwMCq2E= +github.com/pion/srtp/v3 v3.0.6 h1:E2gyj1f5X10sB/qILUGIkL4C2CqK269Xq167PbGCc/4= +github.com/pion/srtp/v3 v3.0.6/go.mod h1:BxvziG3v/armJHAaJ87euvkhHqWe9I7iiOy50K2QkhY= github.com/pion/stun v0.6.1 h1:8lp6YejULeHBF8NmV8e2787BogQhduZugh5PdhDyyN4= github.com/pion/stun v0.6.1/go.mod h1:/hO7APkX4hZKu/D0f2lHzNyvdkTGtIy3NDmLR7kSz/8= github.com/pion/stun/v3 v3.0.0 h1:4h1gwhWLWuZWOJIJR9s2ferRO+W3zA/b6ijOI6mKzUw= @@ -328,39 +288,35 @@ github.com/pion/transport/v2 v2.2.10 h1:ucLBLE8nuxiHfvkFKnkDQRYWYfp8ejf4YBOPfaQp github.com/pion/transport/v2 v2.2.10/go.mod h1:sq1kSLWs+cHW9E+2fJP95QudkzbK7wscs8yYgQToO5E= github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0= github.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uPLGhhz9rwo= -github.com/pion/turn/v4 v4.0.0 h1:qxplo3Rxa9Yg1xXDxxH8xaqcyGUtbHYw4QSCvmFWvhM= -github.com/pion/turn/v4 v4.0.0/go.mod h1:MuPDkm15nYSklKpN8vWJ9W2M0PlyQZqYt1McGuxG7mA= -github.com/pion/webrtc/v4 v4.0.10 h1:Hq/JLjhqLxi+NmCtE8lnRPDr8H4LcNvwg8OxVcdv56Q= -github.com/pion/webrtc/v4 v4.0.10/go.mod h1:ViHLVaNpiuvaH8pdiuQxuA9awuE6KVzAXx3vVWilOck= +github.com/pion/turn/v4 v4.0.2 h1:ZqgQ3+MjP32ug30xAbD6Mn+/K4Sxi3SdNOTFf+7mpps= +github.com/pion/turn/v4 v4.0.2/go.mod h1:pMMKP/ieNAG/fN5cZiN4SDuyKsXtNTr0ccN7IToA1zs= +github.com/pion/webrtc/v4 v4.1.2 h1:mpuUo/EJ1zMNKGE79fAdYNFZBX790KE7kQQpLMjjR54= +github.com/pion/webrtc/v4 v4.1.2/go.mod h1:xsCXiNAmMEjIdFxAYU0MbB3RwRieJsegSB2JZsGN+8U= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/polydawn/refmt v0.89.0 h1:ADJTApkvkeBZsN0tBTx8QjpD9JkmxbKp0cxfr9qszm4= github.com/polydawn/refmt v0.89.0/go.mod h1:/zvteZs/GwLtCgZ4BL6CBsk9IKIlexP43ObX9AxTqTw= github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk= -github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg= +github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= +github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= -github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= -github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= +github.com/prometheus/common v0.64.0 h1:pdZeA+g617P7oGv1CzdTzyeShxAGrTBsolKNOLQPGO4= +github.com/prometheus/common v0.64.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= -github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= +github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= -github.com/quic-go/quic-go v0.50.1 h1:unsgjFIUqW8a2oopkY7YNONpV1gYND6Nt9hnt1PN94Q= -github.com/quic-go/quic-go v0.50.1/go.mod h1:Vim6OmUvlYdwBhXP9ZVrtGmCMWa3wEqhq3NgYrI8b4E= -github.com/quic-go/webtransport-go v0.8.1-0.20241018022711-4ac2c9250e66 h1:4WFk6u3sOT6pLa1kQ50ZVdm8BQFgJNA117cepZxtLIg= -github.com/quic-go/webtransport-go v0.8.1-0.20241018022711-4ac2c9250e66/go.mod h1:Vp72IJajgeOL6ddqrAhmp7IM9zbTcgkQxD/YdxrVwMw= -github.com/raulk/go-watchdog v1.3.0 h1:oUmdlHxdkXRJlwfG0O9omj8ukerm8MEQavSiDTEtBsk= -github.com/raulk/go-watchdog v1.3.0/go.mod h1:fIvOnLbF0b0ZwkB9YU4mOW9Did//4vPZtDqv66NfsMU= +github.com/quic-go/quic-go v0.53.0 h1:QHX46sISpG2S03dPeZBgVIZp8dGagIaiu2FiVYvpCZI= +github.com/quic-go/quic-go v0.53.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY= +github.com/quic-go/webtransport-go v0.9.0 h1:jgys+7/wm6JarGDrW+lD/r9BGqBAmqY/ssklE09bA70= +github.com/quic-go/webtransport-go v0.9.0/go.mod h1:4FUYIiUc75XSsF6HShcLeXXYZJ9AGwo/xh3L8M/P1ao= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= @@ -389,7 +345,6 @@ github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go. github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4= github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw= -github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs= github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg= @@ -402,9 +357,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= @@ -413,7 +366,6 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= -github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= @@ -426,7 +378,6 @@ github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU= github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY= github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= @@ -439,26 +390,22 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.33.0 h1:/FerN9bax5LoK51X/sI0SVYrjSE0/yUL7DpxW4K3FWw= -go.opentelemetry.io/otel v1.33.0/go.mod h1:SUUkR6csvUQl+yjReHu5uM3EtVV7MBm5FHKRlNx4I8I= -go.opentelemetry.io/otel/metric v1.33.0 h1:r+JOocAyeRVXD8lZpjdQjzMadVZp2M4WmQ+5WtEnklQ= -go.opentelemetry.io/otel/metric v1.33.0/go.mod h1:L9+Fyctbp6HFTddIxClbQkjtubW6O9QS3Ann/M82u6M= -go.opentelemetry.io/otel/trace v1.33.0 h1:cCJuF7LRjUFso9LPnEAHJDB2pqzp+hbO8eu1qqW2d/s= -go.opentelemetry.io/otel/trace v1.33.0/go.mod h1:uIcdVUZMpTAmz0tI1z04GoVSezK37CbGV4fr1f2nBck= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/dig v1.18.0 h1:imUL1UiY0Mg4bqbFfsRQO5G4CGRBec/ZujWTvSVp3pw= -go.uber.org/dig v1.18.0/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE= -go.uber.org/fx v1.23.0 h1:lIr/gYWQGfTwGcSXWXu4vP5Ws6iqnNEIY+F/aFzCKTg= -go.uber.org/fx v1.23.0/go.mod h1:o/D9n+2mLP6v1EG+qsdT1O8wKopYAsqZasju97SDFCU= -go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= +go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= +go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= +go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= +go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= +go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= +go.uber.org/dig v1.19.0 h1:BACLhebsYdpQ7IROQ1AGPjrXcP5dF80U3gKoFzbaq/4= +go.uber.org/dig v1.19.0/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE= +go.uber.org/fx v1.24.0 h1:wE8mruvpg2kiiL1Vqd0CC+tr0/24XIB10Iwp2lLWzkg= +go.uber.org/fx v1.24.0/go.mod h1:AmDeGyS+ZARGKM4tlH4FY2Jr63VjbEDJHtqXTGP5hbo= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= -go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= -go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko= +go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U= @@ -477,25 +424,23 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= -golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= -golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= +golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= +golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa h1:t2QcU6V556bFjYgu4L6C+6VrCPyJZ+eyRsABUPs1mz4= -golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa/go.mod h1:BHOTPb3L19zxehTsLoJXVaTktb06DFgmdW6Wb9s8jqk= +golang.org/x/exp v0.0.0-20250606033433-dcc06ee1d476 h1:bsqhLWFR6G6xiQcb+JoGqdKdRU6WzPWmK8E0jxTjzo4= +golang.org/x/exp v0.0.0-20250606033433-dcc06ee1d476/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= -golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w= +golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -512,7 +457,6 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= @@ -520,8 +464,8 @@ golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= -golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= -golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= +golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= +golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -537,27 +481,21 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= -golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= +golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210426080607-c94f62235c83/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -566,8 +504,8 @@ golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= -golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -584,16 +522,15 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= -golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= +golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= -golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= +golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= @@ -603,11 +540,10 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY= -golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY= +golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo= +golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -648,17 +584,14 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= @@ -666,7 +599,7 @@ honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -lukechampine.com/blake3 v1.4.0 h1:xDbKOZCVbnZsfzM6mHSYcGRHZ3YrLDzqz8XnV4uaD5w= -lukechampine.com/blake3 v1.4.0/go.mod h1:MQJNQCTnR+kwOP/JEZSxj3MaQjp80FOFSNMMHXcSeX0= +lukechampine.com/blake3 v1.4.1 h1:I3Smz7gso8w4/TunLKec6K2fn+kyKtDxr/xcQEN84Wg= +lukechampine.com/blake3 v1.4.1/go.mod h1:QFosUxmjB8mnrWFSNwKmvxHpfY72bmD2tQ0kBMM3kwo= sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck= sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= diff --git a/examples/unconfigured/main.go b/examples/unconfigured/main.go new file mode 100644 index 0000000000..edace885db --- /dev/null +++ b/examples/unconfigured/main.go @@ -0,0 +1,42 @@ +package main + +import ( + "context" + "log" + + "github.com/libp2p/go-libp2p/core/host" + + "go.uber.org/fx" + + libp2pfx "github.com/libp2p/go-libp2p/fx" +) + +func main() { + factory := func() host.Host { + var h host.Host + app := fx.New( + fx.NopLogger, + libp2pfx.BlankHost(), + libp2pfx.SwarmNetwork(), + libp2pfx.RandomPeerID(), + libp2pfx.EventBus(), + libp2pfx.InMemoryPeerstore(), + libp2pfx.MultistreamMuxer, + libp2pfx.NullConnectionGater, + libp2pfx.NullResourceManager, + libp2pfx.NullConnManager, + fx.Supply(libp2pfx.MetricsConfig{Disable: true}), + fx.Populate(&h), + libp2pfx.ListenAddrs(), + ) + + app.Start(context.Background()) + return h + } + + h := factory() + + defer h.Close() + + log.Printf("Hello World, my second hosts ID is %s\n", h.ID()) +} diff --git a/fx/options.go b/fx/options.go new file mode 100644 index 0000000000..3d89fd10a4 --- /dev/null +++ b/fx/options.go @@ -0,0 +1,362 @@ +package libp2pfx + +import ( + "context" + "crypto/rand" + "crypto/sha256" + "io" + "slices" + + "github.com/libp2p/go-libp2p/core/connmgr" + "github.com/libp2p/go-libp2p/core/crypto" + "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/core/peerstore" + "github.com/libp2p/go-libp2p/core/pnet" + "github.com/libp2p/go-libp2p/core/protocol" + "github.com/libp2p/go-libp2p/core/sec" + "github.com/libp2p/go-libp2p/core/transport" + blankhost "github.com/libp2p/go-libp2p/p2p/host/blank" + "github.com/libp2p/go-libp2p/p2p/host/eventbus" + "github.com/libp2p/go-libp2p/p2p/host/peerstore/pstoremem" + "github.com/libp2p/go-libp2p/p2p/muxer/yamux" + connmgrImpl "github.com/libp2p/go-libp2p/p2p/net/connmgr" + "github.com/libp2p/go-libp2p/p2p/net/swarm" + tptu "github.com/libp2p/go-libp2p/p2p/net/upgrader" + "github.com/libp2p/go-libp2p/p2p/security/noise" + tls "github.com/libp2p/go-libp2p/p2p/security/tls" + libp2pquic "github.com/libp2p/go-libp2p/p2p/transport/quic" + "github.com/libp2p/go-libp2p/p2p/transport/quicreuse" + "github.com/libp2p/go-libp2p/p2p/transport/tcp" + "github.com/multiformats/go-multiaddr" + mstream "github.com/multiformats/go-multistream" + "github.com/prometheus/client_golang/prometheus" + "github.com/quic-go/quic-go" + "go.uber.org/fx" + "golang.org/x/crypto/hkdf" +) + +type blankHostParams struct { + fx.In + Network network.Network + Mux *mstream.MultistreamMuxer[protocol.ID] + ConnMgr connmgr.ConnManager + EventBus event.Bus + Lifecycle fx.Lifecycle +} + +func BlankHost() fx.Option { + return fx.Provide(func(params blankHostParams) host.Host { + h := blankhost.BlankHost{ + N: params.Network, + M: params.Mux, + ConnMgr: params.ConnMgr, + E: params.EventBus, + // Users can do this manually, but can't opt out of it otherwise. + SkipInitSignedRecord: true, + } + params.Lifecycle.Append(fx.Hook{ + OnStart: func(context.Context) error { + return h.Start() + }, + OnStop: func(context.Context) error { + return h.Close() + }, + }) + return &h + }) +} + +type swarmParams struct { + fx.In + fx.Lifecycle + Local peer.ID + Peerstore peerstore.Peerstore + EventBus event.Bus + ListenAddrs []ListenAddr `group:"listenAddr"` +} + +func SwarmNetwork(opts ...swarm.Option) fx.Option { + return fx.Module("swarm", + fx.Provide(fx.Annotate(func(params swarmParams) (*swarm.Swarm, error) { + s, err := swarm.NewSwarm( + params.Local, + params.Peerstore, + params.EventBus, + opts..., + ) + if err != nil { + return nil, err + } + params.Lifecycle.Append(fx.StartStopHook( + func() error { + addrs := make([]multiaddr.Multiaddr, len(params.ListenAddrs)) + for i, a := range params.ListenAddrs { + addrs[i] = multiaddr.Multiaddr(a) + } + return s.Listen(addrs...) + }, + s.Close, + )) + return s, nil + }, fx.As(new(network.Network)), fx.As(fx.Self()))), + fx.Invoke( + fx.Annotate( + func(swrm *swarm.Swarm, tpts []transport.Transport) error { + for _, t := range tpts { + if err := swrm.AddTransport(t); err != nil { + return err + } + } + return nil + }, + fx.ParamTags("", `group:"transport"`), + )), + ) +} + +type peerIDRes struct { + fx.Out + Peer peer.ID + Key crypto.PrivKey +} + +func RandomPeerID() fx.Option { + return fx.Provide(func() (peerIDRes, error) { + priv, _, err := crypto.GenerateEd25519Key(rand.Reader) + if err != nil { + return peerIDRes{}, err + } + pid, err := peer.IDFromPrivateKey(priv) + if err != nil { + return peerIDRes{}, err + } + return peerIDRes{Peer: pid, Key: priv}, nil + }) +} + +var QUICTransport = fx.Provide( + fx.Annotate( + func(params struct { + fx.In + PrivKey crypto.PrivKey + QUICConnMgr *quicreuse.ConnManager + ConnGater connmgr.ConnectionGater + Rcmgr network.ResourceManager + }) (transport.Transport, error) { + return libp2pquic.NewTransport( + params.PrivKey, + params.QUICConnMgr, + nil, + params.ConnGater, + params.Rcmgr, + ) + }, + fx.ResultTags(`group:"transport"`), + ), +) + +func TCPTransport(opts ...tcp.Option) fx.Option { + return fx.Provide( + fx.Annotate( + func(p struct { + fx.In + Upgrader transport.Upgrader + Rcmgr network.ResourceManager + }) (transport.Transport, error) { + return tcp.NewTCPTransport(p.Upgrader, p.Rcmgr, opts...) + }, + fx.As(new(transport.Transport)), + fx.ResultTags(`group:"transport"`), + ), + ) +} + +func Upgrader(opts ...tptu.Option) fx.Option { + return fx.Provide( + func(p struct { + fx.In + Security []sec.SecureTransport + Muxers []tptu.StreamMuxer + Rcmgr network.ResourceManager + ConnGater connmgr.ConnectionGater + }) (transport.Upgrader, error) { + // Not supporting PSK here since it doesn't work on all transports, + // and there are better ways of authenticating peers. If you need + // PSK, provide the upgrader manually. + var psk pnet.PSK = nil + return tptu.New(p.Security, p.Muxers, psk, p.Rcmgr, p.ConnGater, opts...) + }, + ) +} + +// Security is a helper to provide a list of security transports in a specific order. +func Security(ss ...func() (protocol.ID, fx.Option)) fx.Option { + order := make(map[protocol.ID]int) + var opts []fx.Option + for i, s := range ss { + id, opt := s() + order[id] = i + opts = append(opts, opt) + } + opts = append(opts, + fx.Provide(fx.Annotate(func(unorderedSecurity []sec.SecureTransport) []sec.SecureTransport { + slices.SortFunc(unorderedSecurity, func(a, b sec.SecureTransport) int { + return order[a.ID()] - order[b.ID()] + }) + return unorderedSecurity + }, fx.ParamTags(`group:"unorderedSecurity"`))), + ) + return fx.Options(opts...) +} + +var Noise = func() (protocol.ID, fx.Option) { + return noise.ID, fx.Provide( + fx.Annotate( + func( + p struct { + fx.In + Privkey crypto.PrivKey + Muxers []tptu.StreamMuxer + }) (sec.SecureTransport, error) { + return noise.New(noise.ID, p.Privkey, p.Muxers) + }, + fx.ResultTags(`group:"unorderedSecurity"`), + ), + ) +} + +var TLS = func() (protocol.ID, fx.Option) { + return tls.ID, fx.Provide( + fx.Annotate( + func( + p struct { + fx.In + Privkey crypto.PrivKey + Muxers []tptu.StreamMuxer + }) (sec.SecureTransport, error) { + return tls.New(tls.ID, p.Privkey, p.Muxers) + }, + fx.ResultTags(`group:"unorderedSecurity"`), + ), + ) +} + +var Yamux = fx.Supply( + []tptu.StreamMuxer{{ + ID: yamux.ID, + Muxer: yamux.DefaultTransport, + }}, +) + +type MetricsConfig struct { + Disable bool + PrometheusRegister prometheus.Registerer +} + +var DisableMetrics = fx.Decorate(func(params struct { + fx.In + cfg *MetricsConfig `optional:"true"` +}) *MetricsConfig { + if params.cfg == nil { + params.cfg = new(MetricsConfig) + } + params.cfg.Disable = true + return params.cfg +}) + +var QUICReuseConnManager = fx.Provide( + func(metricsCfg MetricsConfig, key quic.StatelessResetKey, tokenGenerator quic.TokenGeneratorKey, lifecycle fx.Lifecycle) (*quicreuse.ConnManager, error) { + var opts []quicreuse.Option + if !metricsCfg.Disable { + opts = append(opts, quicreuse.EnableMetrics(metricsCfg.PrometheusRegister)) + } + cm, err := quicreuse.NewConnManager(key, tokenGenerator, opts...) + if err != nil { + return nil, err + } + lifecycle.Append(fx.StopHook(cm.Close)) + return cm, nil + }, + func(key crypto.PrivKey) (quic.StatelessResetKey, error) { + var statelessResetKey quic.StatelessResetKey + keyBytes, err := key.Raw() + if err != nil { + return statelessResetKey, err + } + + const statelessResetKeyInfo = "libp2p quic stateless reset key" + keyReader := hkdf.New(sha256.New, keyBytes, nil, []byte(statelessResetKeyInfo)) + if _, err := io.ReadFull(keyReader, statelessResetKey[:]); err != nil { + return statelessResetKey, err + } + return statelessResetKey, nil + }, + func(key crypto.PrivKey) (quic.TokenGeneratorKey, error) { + var tokenKey quic.TokenGeneratorKey + keyBytes, err := key.Raw() + if err != nil { + return tokenKey, err + } + + const tokenGeneratorKeyInfo = "libp2p quic token generator key" + keyReader := hkdf.New(sha256.New, keyBytes, nil, []byte(tokenGeneratorKeyInfo)) + if _, err := io.ReadFull(keyReader, tokenKey[:]); err != nil { + return tokenKey, err + } + return tokenKey, nil + }, +) + +func EventBus(opts ...eventbus.Option) fx.Option { + return fx.Supply(fx.Annotate(eventbus.NewBus(opts...), fx.As(new(event.Bus)))) +} + +func InMemoryPeerstore() fx.Option { + return fx.Provide(func() (peerstore.Peerstore, error) { + return pstoremem.NewPeerstore() + }) +} + +func ConnManager(low, hi int) fx.Option { + return fx.Provide(func() (connmgr.ConnManager, error) { + return connmgrImpl.NewConnManager(low, hi) + }) +} + +var DefaultConnManager = ConnManager(160, 192) + +var NullConnManager = fx.Provide(func() connmgr.ConnManager { + return connmgr.NullConnMgr{} +}) + +var NullResourceManager = fx.Provide(func() network.ResourceManager { + return &network.NullResourceManager{} +}) + +var NullConnectionGater = fx.Provide(func() connmgr.ConnectionGater { + return nil +}) + +var MultistreamMuxer = fx.Provide(func() *mstream.MultistreamMuxer[protocol.ID] { + return mstream.NewMultistreamMuxer[protocol.ID]() +}) + +// New type to specify that these are used for listening. +type ListenAddr multiaddr.Multiaddr + +func ListenAddrs(addrs ...multiaddr.Multiaddr) fx.Option { + return fx.Provide( + fx.Annotate( + func() []ListenAddr { + out := make([]ListenAddr, len(addrs)) + for i, a := range addrs { + out[i] = ListenAddr(a) + } + return out + }, + fx.ResultTags(`group:"listenAddr,flatten"`), + )) +} diff --git a/fx/options_test.go b/fx/options_test.go new file mode 100644 index 0000000000..c25628315e --- /dev/null +++ b/fx/options_test.go @@ -0,0 +1,235 @@ +package libp2pfx + +import ( + "context" + "io" + "testing" + + "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/core/sec" + "github.com/libp2p/go-libp2p/p2p/protocol/identify" + "github.com/libp2p/go-libp2p/p2p/protocol/ping" + "github.com/multiformats/go-multiaddr" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/fx" +) + +func TestConstructHost(t *testing.T) { + app := fx.New( + BlankHost(), + SwarmNetwork(), + RandomPeerID(), + EventBus(), + InMemoryPeerstore(), + NullConnManager, + MultistreamMuxer, + fx.Invoke(func(h host.Host) { + require.NotNil(t, h) + }), + ) + require.NoError(t, app.Start(context.Background())) + defer assert.NoError(t, app.Stop(context.Background())) +} + +func echoTest(t *testing.T, h1, h2 host.Host) { + require.NoError(t, + h1.Connect(context.Background(), peer.AddrInfo{ + ID: h2.ID(), + Addrs: h2.Addrs(), + })) + require.NoError(t, + h2.Connect(context.Background(), peer.AddrInfo{ + ID: h1.ID(), + Addrs: h1.Addrs(), + })) + + h2.SetStreamHandler("/test", func(s network.Stream) { + defer s.Close() + io.Copy(s, s) + }) + + s, err := h1.NewStream(context.Background(), h2.ID(), "/test") + require.NoError(t, err) + + _, err = s.Write([]byte("hello")) + require.NoError(t, err) + require.NoError(t, s.CloseWrite()) + + b, err := io.ReadAll(s) + require.NoError(t, err) + require.Equal(t, "hello", string(b)) +} + +func TestQUICTransport(t *testing.T) { + newHost := func() host.Host { + var h host.Host + app := fx.New( + fx.NopLogger, + BlankHost(), + SwarmNetwork(), + RandomPeerID(), + EventBus(), + InMemoryPeerstore(), + MultistreamMuxer, + QUICTransport, + QUICReuseConnManager, + NullConnectionGater, + NullResourceManager, + NullConnManager, + fx.Supply(MetricsConfig{Disable: true}), + fx.Populate(&h), + ListenAddrs(multiaddr.StringCast("/ip4/127.0.0.1/udp/0/quic-v1")), + ) + + require.NoError(t, app.Start(context.Background())) + t.Cleanup(func() { app.Stop(context.Background()) }) + return h + } + + h1 := newHost() + h2 := newHost() + + echoTest(t, h1, h2) +} + +func TestTCPTransport(t *testing.T) { + newHost := func() host.Host { + var h host.Host + app := fx.New( + fx.NopLogger, + BlankHost(), + SwarmNetwork(), + RandomPeerID(), + EventBus(), + InMemoryPeerstore(), + MultistreamMuxer, + QUICReuseConnManager, + NullConnectionGater, + NullResourceManager, + NullConnManager, + fx.Supply(MetricsConfig{Disable: true}), + fx.Populate(&h), + Upgrader(), + TCPTransport(), + Yamux, + // TODO how to order? + Security( + TLS, + Noise, + ), + // Assert the security order is correct + ListenAddrs(multiaddr.StringCast("/ip4/127.0.0.1/tcp/0")), + ) + + require.NoError(t, app.Start(context.Background())) + t.Cleanup(func() { app.Stop(context.Background()) }) + return h + } + + h1 := newHost() + h2 := newHost() + t.Log(h1.Addrs(), h2.Addrs()) + + echoTest(t, h1, h2) +} + +func TestSecurityOrder(t *testing.T) { + app := fx.New( + RandomPeerID(), + Yamux, + Security( + TLS, + Noise, + ), + // Assert the security order is correct + fx.Invoke(func(security []sec.SecureTransport) { + ids := make([]string, len(security)) + for i, s := range security { + ids[i] = string(s.ID()) + } + assert.Equal(t, []string{"/tls/1.0.0", "/noise"}, ids) + }), + ) + require.NoError(t, app.Start(context.Background())) + require.NoError(t, app.Stop(context.Background())) +} + +func quicOptions() fx.Option { + return fx.Options( + fx.NopLogger, + BlankHost(), + SwarmNetwork(), + RandomPeerID(), + EventBus(), + InMemoryPeerstore(), + MultistreamMuxer, + QUICTransport, + QUICReuseConnManager, + NullConnectionGater, + NullResourceManager, + NullConnManager, + fx.Supply(MetricsConfig{Disable: true}), + ListenAddrs(multiaddr.StringCast("/ip4/127.0.0.1/udp/0/quic-v1")), + ) +} + +func TestPing(t *testing.T) { + newHost := func() (host.Host, *ping.PingService) { + var h host.Host + var s *ping.PingService + opts := quicOptions() + app := fx.New( + opts, + PingService, + fx.Populate(&h), + fx.Populate(&s), + ) + require.NoError(t, app.Start(context.Background())) + t.Cleanup(func() { assert.NoError(t, app.Stop(context.Background())) }) + return h, s + } + + h1, s := newHost() + h2, _ := newHost() + require.NoError(t, h1.Connect(context.Background(), peer.AddrInfo{h2.ID(), h2.Addrs()})) + res := <-s.Ping(context.Background(), h2.ID()) + require.NoError(t, res.Error) + t.Log(res.RTT) +} + +func TestIdentify(t *testing.T) { + newHost := func() (host.Host, identify.IDService, event.Bus) { + var h host.Host + var s identify.IDService + var eb event.Bus + opts := quicOptions() + app := fx.New( + opts, + IdentifyService(), + fx.Populate(&h, &s, &eb), + ) + require.NoError(t, app.Start(context.Background())) + t.Cleanup(func() { assert.NoError(t, app.Stop(context.Background())) }) + return h, s, eb + } + + h1, s, eb1 := newHost() + h2, _, eb2 := newHost() + sub1, err := eb1.Subscribe(new(event.EvtPeerIdentificationCompleted)) + require.NoError(t, err) + + sub2, err := eb2.Subscribe(new(event.EvtPeerIdentificationCompleted)) + require.NoError(t, err) + + require.NoError(t, h1.Connect(context.Background(), peer.AddrInfo{h2.ID(), h2.Addrs()})) + c := h1.Network().Conns()[0] + <-s.IdentifyWait(c) + res := (<-sub1.Out()).(event.EvtPeerIdentificationCompleted) + t.Log(res) + res = (<-sub2.Out()).(event.EvtPeerIdentificationCompleted) + t.Log(res) +} diff --git a/fx/services.go b/fx/services.go new file mode 100644 index 0000000000..d5d6916376 --- /dev/null +++ b/fx/services.go @@ -0,0 +1,25 @@ +package libp2pfx + +import ( + "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/p2p/protocol/identify" + "github.com/libp2p/go-libp2p/p2p/protocol/ping" + "go.uber.org/fx" +) + +func IdentifyService(opts ...identify.Option) fx.Option { + return fx.Provide(func(l fx.Lifecycle, h host.Host) (identify.IDService, error) { + s, err := identify.NewIDService(h, opts...) + if err != nil { + return nil, err + } + l.Append(fx.StartStopHook(s.Start, s.Close)) + return s, nil + }) +} + +var PingService = fx.Provide(func(l fx.Lifecycle, h host.Host) *ping.PingService { + s := &ping.PingService{Host: h} + l.Append(fx.StartStopHook(s.Start, s.Stop)) + return s +}) diff --git a/p2p/host/basic/addrs_manager.go b/p2p/host/basic/addrs_manager.go index 46d2c49662..5c8936d63c 100644 --- a/p2p/host/basic/addrs_manager.go +++ b/p2p/host/basic/addrs_manager.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "io" "net" "slices" "sync" @@ -12,22 +13,30 @@ import ( "github.com/libp2p/go-libp2p/core/event" "github.com/libp2p/go-libp2p/core/network" - "github.com/libp2p/go-libp2p/core/transport" "github.com/libp2p/go-libp2p/p2p/host/basic/internal/backoff" "github.com/libp2p/go-libp2p/p2p/host/eventbus" - libp2pwebrtc "github.com/libp2p/go-libp2p/p2p/transport/webrtc" - libp2pwebtransport "github.com/libp2p/go-libp2p/p2p/transport/webtransport" "github.com/libp2p/go-netroute" ma "github.com/multiformats/go-multiaddr" manet "github.com/multiformats/go-multiaddr/net" "github.com/prometheus/client_golang/prometheus" ) -const maxObservedAddrsPerListenAddr = 5 +var ( + // addrChangeTickrInterval is the interval to recompute host addrs. + addrChangeTickrInterval = 5 * time.Second + // natTypeChageTickrInterval is the interval to recompute host nat type. + natTypeChangeTickrInterval = time.Minute +) type observedAddrsManager interface { - OwnObservedAddrs() []ma.Multiaddr - ObservedAddrsFor(local ma.Multiaddr) []ma.Multiaddr + Addrs(minObservers int) []ma.Multiaddr + AddrsFor(local ma.Multiaddr) []ma.Multiaddr + + Record(conn connMultiaddrs, observed ma.Multiaddr) + RemoveConn(conn connMultiaddrs) + Start() + getNATType() (network.NATDeviceType, network.NATDeviceType) + io.Closer } type hostAddrs struct { @@ -44,7 +53,7 @@ type addrsManager struct { natManager NATManager addrsFactory AddrsFactory listenAddrs func() []ma.Multiaddr - transportForListening func(ma.Multiaddr) transport.Transport + addCertHashes func([]ma.Multiaddr) []ma.Multiaddr observedAddrsManager observedAddrsManager interfaceAddrs *interfaceAddrsCache addrsReachabilityTracker *addrsReachabilityTracker @@ -72,7 +81,8 @@ func newAddrsManager( natmgr NATManager, addrsFactory AddrsFactory, listenAddrs func() []ma.Multiaddr, - transportForListening func(ma.Multiaddr) transport.Transport, + addCertHashes func([]ma.Multiaddr) []ma.Multiaddr, + disableObservedAddrs bool, observedAddrsManager observedAddrsManager, addrsUpdatedChan chan struct{}, client autonatv2Client, @@ -83,8 +93,7 @@ func newAddrsManager( as := &addrsManager{ bus: bus, listenAddrs: listenAddrs, - transportForListening: transportForListening, - observedAddrsManager: observedAddrsManager, + addCertHashes: addCertHashes, natManager: natmgr, addrsFactory: addrsFactory, triggerAddrsUpdateChan: make(chan struct{}, 1), @@ -97,6 +106,25 @@ func newAddrsManager( unknownReachability := network.ReachabilityUnknown as.hostReachability.Store(&unknownReachability) + if !disableObservedAddrs { + if observedAddrsManager != nil { + as.observedAddrsManager = observedAddrsManager + } else { + om, err := NewObservedAddrManager(func() []ma.Multiaddr { + l := as.listenAddrs() + r, err := manet.ResolveUnspecifiedAddresses(l, as.interfaceAddrs.All()) + if err != nil { + return l + } + return append(l, r...) + }) + if err != nil { + return nil, fmt.Errorf("failed to create observed addrs manager: %w", err) + } + as.observedAddrsManager = om + } + } + if client != nil { var metricsTracker MetricsTracker if enableMetrics { @@ -115,6 +143,14 @@ func (a *addrsManager) Start() error { return fmt.Errorf("error starting addrs reachability tracker: %s", err) } } + if a.observedAddrsManager != nil { + a.observedAddrsManager.Start() + err := a.startObservedAddrsWorker() + if err != nil { + a.observedAddrsManager.Close() + return fmt.Errorf("error starting observed addrs worker: %s", err) + } + } return a.startBackgroundWorker() } @@ -133,19 +169,30 @@ func (a *addrsManager) Close() { log.Warnf("error closing addrs reachability tracker: %s", err) } } + if a.observedAddrsManager != nil { + err := a.observedAddrsManager.Close() + if err != nil { + log.Warnf("error closing observed addrs manager: %s", err) + } + } a.wg.Wait() } func (a *addrsManager) NetNotifee() network.Notifiee { - // Updating addrs in sync provides the nice property that - // host.Addrs() just after host.Network().Listen(x) will return x return &network.NotifyBundle{ ListenF: func(network.Network, ma.Multiaddr) { a.triggerAddrsUpdate() }, ListenCloseF: func(network.Network, ma.Multiaddr) { a.triggerAddrsUpdate() }, + DisconnectedF: func(_ network.Network, conn network.Conn) { + if a.observedAddrsManager != nil { + a.observedAddrsManager.RemoveConn(conn) + } + }, } } func (a *addrsManager) triggerAddrsUpdate() { + // Updating addrs in sync provides the nice property that + // host.Addrs() just after host.Network().Listen(x) will return x a.updateAddrs(false, nil) select { case a.triggerAddrsUpdateChan <- struct{}{}: @@ -153,34 +200,50 @@ func (a *addrsManager) triggerAddrsUpdate() { } } -func (a *addrsManager) startBackgroundWorker() error { +func closeIfError(err error, closer io.Closer, name string) error { + if err != nil { + err1 := closer.Close() + if err1 != nil { + err1 = fmt.Errorf("error closing %s: %w", name, err1) + } + return errors.Join(err, err1) + } + return nil +} + +func (a *addrsManager) startObservedAddrsWorker() (retErr error) { + identifySub, err := a.bus.Subscribe(new(event.EvtPeerIdentificationCompleted), eventbus.Name("addrs-manager")) + if err != nil { + return fmt.Errorf("error subscribing to autonat reachability: %s", err) + } + defer func() { retErr = closeIfError(retErr, identifySub, "identify subscription") }() + + natTypeEmitter, err := a.bus.Emitter(new(event.EvtNATDeviceTypeChanged), eventbus.Stateful) + if err != nil { + return fmt.Errorf("error creating nat type emitter: %s", err) + } + + a.wg.Add(1) + go a.observedAddrsWorker(identifySub, natTypeEmitter) + return nil +} + +func (a *addrsManager) startBackgroundWorker() (retErr error) { autoRelayAddrsSub, err := a.bus.Subscribe(new(event.EvtAutoRelayAddrsUpdated), eventbus.Name("addrs-manager")) if err != nil { return fmt.Errorf("error subscribing to auto relay addrs: %s", err) } + defer func() { retErr = closeIfError(retErr, autoRelayAddrsSub, "autorelay subscription") }() autonatReachabilitySub, err := a.bus.Subscribe(new(event.EvtLocalReachabilityChanged), eventbus.Name("addrs-manager")) if err != nil { - err1 := autoRelayAddrsSub.Close() - if err1 != nil { - err1 = fmt.Errorf("error closign autorelaysub: %w", err1) - } - err = fmt.Errorf("error subscribing to autonat reachability: %s", err) - return errors.Join(err, err1) + return fmt.Errorf("error subscribing to autonat reachability: %s", err) } + defer func() { retErr = closeIfError(retErr, autonatReachabilitySub, "autonatReachability subscription") }() emitter, err := a.bus.Emitter(new(event.EvtHostReachableAddrsChanged), eventbus.Stateful) if err != nil { - err1 := autoRelayAddrsSub.Close() - if err1 != nil { - err1 = fmt.Errorf("error closing autorelaysub: %w", err1) - } - err2 := autonatReachabilitySub.Close() - if err2 != nil { - err2 = fmt.Errorf("error closing autonat reachability: %w", err1) - } - err = fmt.Errorf("error subscribing to autonat reachability: %s", err) - return errors.Join(err, err1, err2) + return fmt.Errorf("error creating reachability subscriber: %s", err) } var relayAddrs []ma.Multiaddr @@ -209,8 +272,11 @@ func (a *addrsManager) startBackgroundWorker() error { return nil } -func (a *addrsManager) background(autoRelayAddrsSub, autonatReachabilitySub event.Subscription, - emitter event.Emitter, relayAddrs []ma.Multiaddr, +func (a *addrsManager) background( + autoRelayAddrsSub, + autonatReachabilitySub event.Subscription, + emitter event.Emitter, + relayAddrs []ma.Multiaddr, ) { defer a.wg.Done() defer func() { @@ -222,6 +288,10 @@ func (a *addrsManager) background(autoRelayAddrsSub, autonatReachabilitySub even if err != nil { log.Warnf("error closing autonat reachability sub: %s", err) } + err = emitter.Close() + if err != nil { + log.Warnf("error closing host reachability emitter: %s", err) + } }() ticker := time.NewTicker(addrChangeTickrInterval) @@ -249,6 +319,38 @@ func (a *addrsManager) background(autoRelayAddrsSub, autonatReachabilitySub even } } +func (a *addrsManager) observedAddrsWorker(identifySub event.Subscription, natTypeEmitter event.Emitter) { + defer a.wg.Done() + defer func() { + err := identifySub.Close() + if err != nil { + log.Warnf("error closing identify sub: %s", err) + } + err = natTypeEmitter.Close() + if err != nil { + log.Warnf("error closing nat emitter: %s", err) + } + }() + natTypeTicker := time.NewTicker(natTypeChangeTickrInterval) + defer natTypeTicker.Stop() + var udpNATType, tcpNATType network.NATDeviceType + for { + select { + case e := <-identifySub.Out(): + evt := e.(event.EvtPeerIdentificationCompleted) + a.observedAddrsManager.Record(evt.Conn, evt.ObservedAddr) + case <-natTypeTicker.C: + if *a.hostReachability.Load() == network.ReachabilityPrivate { + newUDPNAT, newTCPNAT := a.observedAddrsManager.getNATType() + a.notifyNATTypeChanged(natTypeEmitter, newUDPNAT, newTCPNAT, udpNATType, tcpNATType) + udpNATType, tcpNATType = newUDPNAT, newTCPNAT + } + case <-a.ctx.Done(): + return + } + } +} + // updateAddrs updates the addresses of the host and returns the new updated // addrs func (a *addrsManager) updateAddrs(updateRelayAddrs bool, relayAddrs []ma.Multiaddr) hostAddrs { @@ -361,10 +463,9 @@ func (a *addrsManager) getAddrs(localAddrs []ma.Multiaddr, relayAddrs []ma.Multi func (a *addrsManager) HolePunchAddrs() []ma.Multiaddr { addrs := a.DirectAddrs() addrs = slices.Clone(a.addrsFactory(addrs)) - // AllAddrs may ignore observed addresses in favour of NAT mappings. - // Use both for hole punching. if a.observedAddrsManager != nil { - addrs = append(addrs, a.observedAddrsManager.OwnObservedAddrs()...) + // For holepunching, include all the best addresses we know even ones with only 1 observer. + addrs = append(addrs, a.observedAddrsManager.Addrs(1)...) } addrs = ma.Unique(addrs) return slices.DeleteFunc(addrs, func(a ma.Multiaddr) bool { return !manet.IsPublicAddr(a) }) @@ -399,7 +500,12 @@ func (a *addrsManager) getLocalAddrs() []ma.Multiaddr { finalAddrs := make([]ma.Multiaddr, 0, 8) finalAddrs = a.appendPrimaryInterfaceAddrs(finalAddrs, listenAddrs) - finalAddrs = a.appendNATAddrs(finalAddrs, listenAddrs, a.interfaceAddrs.All()) + if a.natManager != nil { + finalAddrs = a.appendNATAddrs(finalAddrs, listenAddrs) + } + if a.observedAddrsManager != nil { + finalAddrs = a.appendObservedAddrs(finalAddrs, listenAddrs, a.interfaceAddrs.All()) + } // Remove "/p2p-circuit" addresses from the list. // The p2p-circuit listener reports its address as just /p2p-circuit. This is @@ -439,114 +545,50 @@ func (a *addrsManager) appendPrimaryInterfaceAddrs(dst []ma.Multiaddr, listenAdd // Inferring WebTransport from QUIC depends on the observed address manager. // // TODO: Merge the natmgr and identify.ObservedAddrManager in to one NatMapper module. -func (a *addrsManager) appendNATAddrs(dst []ma.Multiaddr, listenAddrs []ma.Multiaddr, ifaceAddrs []ma.Multiaddr) []ma.Multiaddr { - var obsAddrs []ma.Multiaddr +func (a *addrsManager) appendNATAddrs(dst []ma.Multiaddr, listenAddrs []ma.Multiaddr) []ma.Multiaddr { for _, listenAddr := range listenAddrs { - var natAddr ma.Multiaddr - if a.natManager != nil { - natAddr = a.natManager.GetMapping(listenAddr) - } - - // The order of the cases below is important. - switch { - case natAddr == nil: // no nat mapping - dst = a.appendObservedAddrs(dst, listenAddr, ifaceAddrs) - case manet.IsIPUnspecified(natAddr): - log.Infof("NAT device reported an unspecified IP as it's external address: %s", natAddr) - _, natRest := ma.SplitFirst(natAddr) - obsAddrs = a.appendObservedAddrs(obsAddrs[:0], listenAddr, ifaceAddrs) - for _, addr := range obsAddrs { - obsIP, _ := ma.SplitFirst(addr) - if obsIP != nil && manet.IsPublicAddr(obsIP.Multiaddr()) { - dst = append(dst, obsIP.Encapsulate(natRest)) - } - } - // This is !Public as opposed to IsPrivate intentionally. - // Public is a more restrictive classification in some cases, like IPv6 addresses which only - // consider unicast IPv6 addresses allocated so far as public(2000::/3). - case !manet.IsPublicAddr(natAddr): // nat reported non public addr(maybe CGNAT?) - // use both NAT and observed addr - dst = append(dst, natAddr) - dst = a.appendObservedAddrs(dst, listenAddr, ifaceAddrs) - default: // public addr + natAddr := a.natManager.GetMapping(listenAddr) + if natAddr != nil { dst = append(dst, natAddr) } } return dst } -func (a *addrsManager) appendObservedAddrs(dst []ma.Multiaddr, listenAddr ma.Multiaddr, ifaceAddrs []ma.Multiaddr) []ma.Multiaddr { - if a.observedAddrsManager == nil { - return dst - } - // Add it for the listenAddr first. +func (a *addrsManager) appendObservedAddrs(dst []ma.Multiaddr, listenAddrs, ifaceAddrs []ma.Multiaddr) []ma.Multiaddr { + // Add it for all the listenAddr first. // listenAddr maybe unspecified. That's okay as connections on UDP transports // will have the unspecified address as the local address. - obsAddrs := a.observedAddrsManager.ObservedAddrsFor(listenAddr) - if len(obsAddrs) > maxObservedAddrsPerListenAddr { - obsAddrs = obsAddrs[:maxObservedAddrsPerListenAddr] + for _, la := range listenAddrs { + obsAddrs := a.observedAddrsManager.AddrsFor(la) + dst = append(dst, obsAddrs...) } - dst = append(dst, obsAddrs...) // if it can be resolved into more addresses, add them too - resolved, err := manet.ResolveUnspecifiedAddress(listenAddr, ifaceAddrs) + resolved, err := manet.ResolveUnspecifiedAddresses(listenAddrs, ifaceAddrs) if err != nil { - log.Warnf("failed to resolve listen addr %s, %s: %s", listenAddr, ifaceAddrs, err) + log.Warnf("failed to resolve listen addr %s, %s: %s", listenAddrs, ifaceAddrs, err) return dst } for _, addr := range resolved { - obsAddrs = a.observedAddrsManager.ObservedAddrsFor(addr) - if len(obsAddrs) > maxObservedAddrsPerListenAddr { - obsAddrs = obsAddrs[:maxObservedAddrsPerListenAddr] - } - dst = append(dst, obsAddrs...) + dst = append(dst, a.observedAddrsManager.AddrsFor(addr)...) } return dst } -func (a *addrsManager) addCertHashes(addrs []ma.Multiaddr) []ma.Multiaddr { - if a.transportForListening == nil { - return addrs - } - - // TODO(sukunrt): Move this to swarm. - // There are two parts to determining our external address - // 1. From the NAT device, or identify, or other such STUN like mechanism. - // All that matters here is (internal_ip, internal_port, tcp) => (external_ip, external_port, tcp) - // The rest of the address should be cut and appended to the external one. - // 2. The user provides us with the address (/ip4/1.2.3.4/udp/1/webrtc-direct) and we add the certhash. - // This API should be where the transports are, i.e. swarm. - // - // It would have been nice to remove this completely and just work with - // mapping the interface thinwaist addresses (tcp, 192.168.18.18:4000 => 1.2.3.4:4577) - // but that is only convenient if we're using the same port for listening on - // all transports which share the same thinwaist protocol. If you listen - // on 4001 for tcp, and 4002 for websocket, then it's a terrible API. - type addCertHasher interface { - AddCertHashes(m ma.Multiaddr) (ma.Multiaddr, bool) - } - - for i, addr := range addrs { - wtOK, wtN := libp2pwebtransport.IsWebtransportMultiaddr(addr) - webrtcOK, webrtcN := libp2pwebrtc.IsWebRTCDirectMultiaddr(addr) - if (wtOK && wtN == 0) || (webrtcOK && webrtcN == 0) { - t := a.transportForListening(addr) - if t == nil { - continue - } - tpt, ok := t.(addCertHasher) - if !ok { - continue - } - addrWithCerthash, added := tpt.AddCertHashes(addr) - if !added { - log.Warnf("Couldn't add certhashes to multiaddr: %s", addr) - continue - } - addrs[i] = addrWithCerthash - } +func (a *addrsManager) notifyNATTypeChanged(emitter event.Emitter, newUDPNAT, newTCPNAT, oldUDPNAT, oldTCPNAT network.NATDeviceType) { + if newUDPNAT != oldUDPNAT { + emitter.Emit(event.EvtNATDeviceTypeChanged{ + TransportProtocol: network.NATTransportUDP, + NatDeviceType: newUDPNAT, + }) + } + if newTCPNAT != oldTCPNAT { + emitter.Emit(event.EvtNATDeviceTypeChanged{ + TransportProtocol: network.NATTransportTCP, + NatDeviceType: newTCPNAT, + }) } - return addrs } func areAddrsDifferent(prev, current []ma.Multiaddr) bool { diff --git a/p2p/host/basic/addrs_manager_test.go b/p2p/host/basic/addrs_manager_test.go index 299a9d814f..b3c004d38d 100644 --- a/p2p/host/basic/addrs_manager_test.go +++ b/p2p/host/basic/addrs_manager_test.go @@ -13,129 +13,11 @@ import ( "github.com/libp2p/go-libp2p/p2p/host/eventbus" "github.com/libp2p/go-libp2p/p2p/protocol/autonatv2" ma "github.com/multiformats/go-multiaddr" - manet "github.com/multiformats/go-multiaddr/net" "github.com/prometheus/client_golang/prometheus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func TestAppendNATAddrs(t *testing.T) { - if1, if2 := ma.StringCast("/ip4/192.168.0.100"), ma.StringCast("/ip4/1.1.1.1") - ifaceAddrs := []ma.Multiaddr{if1, if2} - tcpListenAddr, udpListenAddr := ma.StringCast("/ip4/0.0.0.0/tcp/1"), ma.StringCast("/ip4/0.0.0.0/udp/2/quic-v1") - cases := []struct { - Name string - Listen ma.Multiaddr - Nat ma.Multiaddr - ObsAddrFunc func(ma.Multiaddr) []ma.Multiaddr - Expected []ma.Multiaddr - }{ - { - Name: "nat map success", - // nat mapping success, obsaddress ignored - Listen: ma.StringCast("/ip4/0.0.0.0/udp/1/quic-v1"), - Nat: ma.StringCast("/ip4/1.1.1.1/udp/10/quic-v1"), - ObsAddrFunc: func(_ ma.Multiaddr) []ma.Multiaddr { - return []ma.Multiaddr{ma.StringCast("/ip4/2.2.2.2/udp/100/quic-v1")} - }, - Expected: []ma.Multiaddr{ma.StringCast("/ip4/1.1.1.1/udp/10/quic-v1")}, - }, - { - Name: "nat map failure", - // nat mapping fails, obs addresses added - Listen: ma.StringCast("/ip4/0.0.0.0/tcp/1"), - Nat: nil, - ObsAddrFunc: func(a ma.Multiaddr) []ma.Multiaddr { - ipC, _ := ma.SplitFirst(a) - ip := ipC.Multiaddr() - switch { - case ip.Equal(if1): - return []ma.Multiaddr{ma.StringCast("/ip4/2.2.2.2/tcp/100")} - case ip.Equal(if2): - return []ma.Multiaddr{ma.StringCast("/ip4/3.3.3.3/tcp/100")} - default: - return []ma.Multiaddr{} - } - }, - Expected: []ma.Multiaddr{ma.StringCast("/ip4/2.2.2.2/tcp/100"), ma.StringCast("/ip4/3.3.3.3/tcp/100")}, - }, - { - Name: "if addrs ignored if not listening on unspecified", - // nat mapping fails, obs addresses added - Listen: ma.StringCast("/ip4/192.168.1.1/tcp/1"), - Nat: nil, - ObsAddrFunc: func(a ma.Multiaddr) []ma.Multiaddr { - ipC, _ := ma.SplitFirst(a) - ip := ipC.Multiaddr() - switch { - case ip.Equal(if1): - return []ma.Multiaddr{ma.StringCast("/ip4/2.2.2.2/tcp/100")} - case ip.Equal(if2): - return []ma.Multiaddr{ma.StringCast("/ip4/3.3.3.3/tcp/100")} - case ip.Equal(ma.StringCast("/ip4/192.168.1.1")): - return []ma.Multiaddr{ma.StringCast("/ip4/4.4.4.4/tcp/100")} - default: - return []ma.Multiaddr{} - } - }, - Expected: []ma.Multiaddr{ma.StringCast("/ip4/4.4.4.4/tcp/100")}, - }, - { - Name: "nat map success but CGNAT", - // nat addr added, obs address added with nat provided port - Listen: tcpListenAddr, - Nat: ma.StringCast("/ip4/100.100.1.1/tcp/100"), - ObsAddrFunc: func(a ma.Multiaddr) []ma.Multiaddr { - ipC, _ := ma.SplitFirst(a) - ip := ipC.Multiaddr() - if ip.Equal(if1) { - return []ma.Multiaddr{ma.StringCast("/ip4/2.2.2.2/tcp/20")} - } - return []ma.Multiaddr{ma.StringCast("/ip4/3.3.3.3/tcp/30")} - }, - Expected: []ma.Multiaddr{ - ma.StringCast("/ip4/100.100.1.1/tcp/100"), - ma.StringCast("/ip4/2.2.2.2/tcp/20"), - ma.StringCast("/ip4/3.3.3.3/tcp/30"), - }, - }, - { - Name: "uses unspecified address for obs address", - // observed address manager should be queries with both specified and unspecified addresses - // udp observed addresses are mapped to unspecified addresses - Listen: udpListenAddr, - Nat: nil, - ObsAddrFunc: func(a ma.Multiaddr) []ma.Multiaddr { - if manet.IsIPUnspecified(a) { - return []ma.Multiaddr{ma.StringCast("/ip4/3.3.3.3/udp/20/quic-v1")} - } - return []ma.Multiaddr{ma.StringCast("/ip4/2.2.2.2/udp/20/quic-v1")} - }, - Expected: []ma.Multiaddr{ - ma.StringCast("/ip4/2.2.2.2/udp/20/quic-v1"), - ma.StringCast("/ip4/3.3.3.3/udp/20/quic-v1"), - }, - }, - } - for _, tc := range cases { - t.Run(tc.Name, func(t *testing.T) { - as := &addrsManager{ - natManager: &mockNatManager{ - GetMappingFunc: func(_ ma.Multiaddr) ma.Multiaddr { - return tc.Nat - }, - }, - observedAddrsManager: &mockObservedAddrs{ - ObservedAddrsForFunc: tc.ObsAddrFunc, - }, - } - res := as.appendNATAddrs(nil, []ma.Multiaddr{tc.Listen}, ifaceAddrs) - res = ma.Unique(res) - require.ElementsMatch(t, tc.Expected, res, "%s\n%s", tc.Expected, res) - }) - } -} - type mockNatManager struct { GetMappingFunc func(addr ma.Multiaddr) ma.Multiaddr } @@ -158,23 +40,38 @@ func (*mockNatManager) HasDiscoveredNAT() bool { var _ NATManager = &mockNatManager{} type mockObservedAddrs struct { - OwnObservedAddrsFunc func() []ma.Multiaddr - ObservedAddrsForFunc func(ma.Multiaddr) []ma.Multiaddr + AddrsFunc func() []ma.Multiaddr + AddrsForFunc func(ma.Multiaddr) []ma.Multiaddr } -func (m *mockObservedAddrs) OwnObservedAddrs() []ma.Multiaddr { - return m.OwnObservedAddrsFunc() -} +// Record implements observedAddrsManager. +func (m *mockObservedAddrs) Record(_ connMultiaddrs, _ ma.Multiaddr) {} -func (m *mockObservedAddrs) ObservedAddrsFor(local ma.Multiaddr) []ma.Multiaddr { - return m.ObservedAddrsForFunc(local) +// Start implements observedAddrsManager. +func (m *mockObservedAddrs) Start() {} + +// getNATType implements observedAddrsManager. +func (m *mockObservedAddrs) getNATType() (network.NATDeviceType, network.NATDeviceType) { + return network.NATDeviceTypeUnknown, network.NATDeviceTypeUnknown } +// removeConn implements observedAddrsManager. +func (m *mockObservedAddrs) RemoveConn(_ connMultiaddrs) {} + +func (m *mockObservedAddrs) Addrs(int) []ma.Multiaddr { return m.AddrsFunc() } + +func (m *mockObservedAddrs) AddrsFor(local ma.Multiaddr) []ma.Multiaddr { return m.AddrsForFunc(local) } + +func (m *mockObservedAddrs) Close() error { return nil } + +var _ observedAddrsManager = &mockObservedAddrs{} + type addrsManagerArgs struct { NATManager NATManager AddrsFactory AddrsFactory ObservedAddrsManager observedAddrsManager ListenAddrs func() []ma.Multiaddr + AddCertHashes func([]ma.Multiaddr) []ma.Multiaddr AutoNATClient autonatv2Client Bus event.Bus } @@ -185,7 +82,7 @@ type addrsManagerTestCase struct { PushReachability func(rch network.Reachability) } -func newAddrsManagerTestCase(t *testing.T, args addrsManagerArgs) addrsManagerTestCase { +func newAddrsManagerTestCase(tb testing.TB, args addrsManagerArgs) addrsManagerTestCase { eb := args.Bus if eb == nil { eb = eventbus.NewBus() @@ -194,28 +91,45 @@ func newAddrsManagerTestCase(t *testing.T, args addrsManagerArgs) addrsManagerTe args.AddrsFactory = func(addrs []ma.Multiaddr) []ma.Multiaddr { return addrs } } addrsUpdatedChan := make(chan struct{}, 1) + + addCertHashes := func(addrs []ma.Multiaddr) []ma.Multiaddr { + return addrs + } + if args.AddCertHashes != nil { + addCertHashes = args.AddCertHashes + } am, err := newAddrsManager( - eb, args.NATManager, args.AddrsFactory, args.ListenAddrs, nil, args.ObservedAddrsManager, addrsUpdatedChan, args.AutoNATClient, true, prometheus.DefaultRegisterer, + eb, + args.NATManager, + args.AddrsFactory, + args.ListenAddrs, + addCertHashes, + false, + args.ObservedAddrsManager, + addrsUpdatedChan, + args.AutoNATClient, + true, + prometheus.DefaultRegisterer, ) - require.NoError(t, err) + require.NoError(tb, err) - require.NoError(t, am.Start()) + require.NoError(tb, am.Start()) raEm, err := eb.Emitter(new(event.EvtAutoRelayAddrsUpdated), eventbus.Stateful) - require.NoError(t, err) + require.NoError(tb, err) rchEm, err := eb.Emitter(new(event.EvtLocalReachabilityChanged), eventbus.Stateful) - require.NoError(t, err) + require.NoError(tb, err) - t.Cleanup(am.Close) + tb.Cleanup(am.Close) return addrsManagerTestCase{ addrsManager: am, PushRelay: func(relayAddrs []ma.Multiaddr) { err := raEm.Emit(event.EvtAutoRelayAddrsUpdated{RelayAddrs: relayAddrs}) - require.NoError(t, err) + require.NoError(tb, err) }, PushReachability: func(rch network.Reachability) { err := rchEm.Emit(event.EvtLocalReachabilityChanged{Reachability: rch}) - require.NoError(t, err) + require.NoError(tb, err) }, } } @@ -225,6 +139,7 @@ func TestAddrsManager(t *testing.T) { lhtcp := ma.StringCast("/ip4/127.0.0.1/tcp/1") publicQUIC := ma.StringCast("/ip4/1.2.3.4/udp/1/quic-v1") + publicQUIC2 := ma.StringCast("/ip4/1.2.3.4/udp/2/quic-v1") publicTCP := ma.StringCast("/ip4/1.2.3.4/tcp/1") t.Run("only nat", func(t *testing.T) { @@ -258,23 +173,25 @@ func TestAddrsManager(t *testing.T) { }, }, ObservedAddrsManager: &mockObservedAddrs{ - ObservedAddrsForFunc: func(addr ma.Multiaddr) []ma.Multiaddr { + AddrsForFunc: func(addr ma.Multiaddr) []ma.Multiaddr { if _, err := addr.ValueForProtocol(ma.P_TCP); err == nil { return []ma.Multiaddr{publicTCP} } + if _, err := addr.ValueForProtocol(ma.P_UDP); err == nil { + return []ma.Multiaddr{publicQUIC2} + } return nil }, }, ListenAddrs: func() []ma.Multiaddr { return []ma.Multiaddr{lhquic, lhtcp} }, }) require.EventuallyWithT(t, func(collect *assert.CollectT) { - expected := []ma.Multiaddr{lhquic, lhtcp, publicQUIC, publicTCP} + expected := []ma.Multiaddr{lhquic, lhtcp, publicQUIC, publicTCP, publicQUIC2} assert.ElementsMatch(collect, am.Addrs(), expected, "%s\n%s", am.Addrs(), expected) }, 5*time.Second, 50*time.Millisecond) }) - t.Run("nat returns unspecified addr", func(t *testing.T) { + t.Run("nat returns private addr addr", func(t *testing.T) { quicPort1 := ma.StringCast("/ip4/3.3.3.3/udp/1/quic-v1") - quicPort2 := ma.StringCast("/ip4/3.3.3.3/udp/2/quic-v1") // port from nat, IP from observed addr am := newAddrsManagerTestCase(t, addrsManagerArgs{ NATManager: &mockNatManager{ @@ -286,7 +203,7 @@ func TestAddrsManager(t *testing.T) { }, }, ObservedAddrsManager: &mockObservedAddrs{ - ObservedAddrsForFunc: func(addr ma.Multiaddr) []ma.Multiaddr { + AddrsForFunc: func(addr ma.Multiaddr) []ma.Multiaddr { if addr.Equal(lhquic) { return []ma.Multiaddr{quicPort1} } @@ -295,7 +212,7 @@ func TestAddrsManager(t *testing.T) { }, ListenAddrs: func() []ma.Multiaddr { return []ma.Multiaddr{lhquic} }, }) - expected := []ma.Multiaddr{lhquic, quicPort2} + expected := []ma.Multiaddr{lhquic, quicPort1} require.EventuallyWithT(t, func(collect *assert.CollectT) { assert.ElementsMatch(collect, am.Addrs(), expected, "%s\n%s", am.Addrs(), expected) }, 5*time.Second, 50*time.Millisecond) @@ -303,7 +220,7 @@ func TestAddrsManager(t *testing.T) { t.Run("only observed addrs", func(t *testing.T) { am := newAddrsManagerTestCase(t, addrsManagerArgs{ ObservedAddrsManager: &mockObservedAddrs{ - ObservedAddrsForFunc: func(addr ma.Multiaddr) []ma.Multiaddr { + AddrsForFunc: func(addr ma.Multiaddr) []ma.Multiaddr { if addr.Equal(lhtcp) { return []ma.Multiaddr{publicTCP} } @@ -322,38 +239,10 @@ func TestAddrsManager(t *testing.T) { }, 5*time.Second, 50*time.Millisecond) }) - t.Run("observed addrs limit", func(t *testing.T) { - quicAddrs := []ma.Multiaddr{ - ma.StringCast("/ip4/1.2.3.4/udp/1/quic-v1"), - ma.StringCast("/ip4/1.2.3.4/udp/2/quic-v1"), - ma.StringCast("/ip4/1.2.3.4/udp/3/quic-v1"), - ma.StringCast("/ip4/1.2.3.4/udp/4/quic-v1"), - ma.StringCast("/ip4/1.2.3.4/udp/5/quic-v1"), - ma.StringCast("/ip4/1.2.3.4/udp/6/quic-v1"), - ma.StringCast("/ip4/1.2.3.4/udp/7/quic-v1"), - ma.StringCast("/ip4/1.2.3.4/udp/8/quic-v1"), - ma.StringCast("/ip4/1.2.3.4/udp/9/quic-v1"), - ma.StringCast("/ip4/1.2.3.4/udp/10/quic-v1"), - } - am := newAddrsManagerTestCase(t, addrsManagerArgs{ - ObservedAddrsManager: &mockObservedAddrs{ - ObservedAddrsForFunc: func(_ ma.Multiaddr) []ma.Multiaddr { - return quicAddrs - }, - }, - ListenAddrs: func() []ma.Multiaddr { return []ma.Multiaddr{lhquic} }, - }) - am.triggerAddrsUpdate() - expected := []ma.Multiaddr{lhquic} - expected = append(expected, quicAddrs[:maxObservedAddrsPerListenAddr]...) - require.EventuallyWithT(t, func(collect *assert.CollectT) { - assert.ElementsMatch(collect, am.Addrs(), expected, "%s\n%s", am.Addrs(), expected) - }, 5*time.Second, 50*time.Millisecond) - }) t.Run("public addrs removed when private", func(t *testing.T) { am := newAddrsManagerTestCase(t, addrsManagerArgs{ ObservedAddrsManager: &mockObservedAddrs{ - ObservedAddrsForFunc: func(_ ma.Multiaddr) []ma.Multiaddr { + AddrsForFunc: func(_ ma.Multiaddr) []ma.Multiaddr { return []ma.Multiaddr{publicQUIC} }, }, @@ -395,7 +284,7 @@ func TestAddrsManager(t *testing.T) { return nil }, ObservedAddrsManager: &mockObservedAddrs{ - ObservedAddrsForFunc: func(_ ma.Multiaddr) []ma.Multiaddr { + AddrsForFunc: func(_ ma.Multiaddr) []ma.Multiaddr { return []ma.Multiaddr{publicQUIC} }, }, @@ -546,3 +435,21 @@ func BenchmarkRemoveIfNotInSource(b *testing.B) { removeNotInSource(slices.Clone(addrs[:5]), addrs[:]) } } + +func BenchmarkUpdateAddrs(b *testing.B) { + publicQUIC, _ := ma.NewMultiaddr("/ip4/1.2.3.4/udp/1234/quic-v1") + publicQUIC2, _ := ma.NewMultiaddr("/ip4/1.2.3.4/udp/1235/quic-v1") + publicTCP, _ := ma.NewMultiaddr("/ip4/1.2.3.4/tcp/1234") + am := newAddrsManagerTestCase(b, addrsManagerArgs{ + ListenAddrs: func() []ma.Multiaddr { + return []ma.Multiaddr{publicQUIC, publicQUIC2, publicTCP} + }, + }) + am.Close() + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + am.updateAddrs(false, nil) + } +} diff --git a/p2p/host/basic/basic_host.go b/p2p/host/basic/basic_host.go index ee55054a03..dfe7b0133e 100644 --- a/p2p/host/basic/basic_host.go +++ b/p2p/host/basic/basic_host.go @@ -18,7 +18,6 @@ import ( "github.com/libp2p/go-libp2p/core/peerstore" "github.com/libp2p/go-libp2p/core/protocol" "github.com/libp2p/go-libp2p/core/record" - "github.com/libp2p/go-libp2p/core/transport" "github.com/libp2p/go-libp2p/p2p/host/autonat" "github.com/libp2p/go-libp2p/p2p/host/eventbus" "github.com/libp2p/go-libp2p/p2p/host/pstoremanager" @@ -28,8 +27,6 @@ import ( "github.com/libp2p/go-libp2p/p2p/protocol/holepunch" "github.com/libp2p/go-libp2p/p2p/protocol/identify" "github.com/libp2p/go-libp2p/p2p/protocol/ping" - libp2pwebrtc "github.com/libp2p/go-libp2p/p2p/transport/webrtc" - libp2pwebtransport "github.com/libp2p/go-libp2p/p2p/transport/webtransport" "github.com/prometheus/client_golang/prometheus" logging "github.com/ipfs/go-log/v2" @@ -38,9 +35,6 @@ import ( msmux "github.com/multiformats/go-multistream" ) -// addrChangeTickrInterval is the interval between two address change ticks. -var addrChangeTickrInterval = 5 * time.Second - var log = logging.Logger("basichost") var ( @@ -232,12 +226,6 @@ func NewHost(n network.Network, opts *HostOpts) (*BasicHost, error) { if opts.NATManager != nil { natmgr = opts.NATManager(h.Network()) } - var tfl func(ma.Multiaddr) transport.Transport - if s, ok := h.Network().(interface { - TransportForListening(ma.Multiaddr) transport.Transport - }); ok { - tfl = s.TransportForListening - } if opts.AutoNATv2 != nil { h.autonatv2 = opts.AutoNATv2 @@ -247,13 +235,25 @@ func NewHost(n network.Network, opts *HostOpts) (*BasicHost, error) { if h.autonatv2 != nil { autonatv2Client = h.autonatv2 } + + // Create addCertHashes function with interface assertion for swarm + addCertHashesFunc := func(addrs []ma.Multiaddr) []ma.Multiaddr { + return addrs + } + if swarm, ok := h.Network().(interface { + AddCertHashes(addrs []ma.Multiaddr) []ma.Multiaddr + }); ok { + addCertHashesFunc = swarm.AddCertHashes + } + h.addressManager, err = newAddrsManager( h.eventbus, natmgr, addrFactory, h.Network().ListenAddresses, - tfl, - h.ids, + addCertHashesFunc, + opts.DisableIdentifyAddressDiscovery, + nil, h.addrsUpdatedChan, autonatv2Client, opts.EnableMetrics, @@ -743,23 +743,6 @@ func (h *BasicHost) Addrs() []ma.Multiaddr { return h.addressManager.Addrs() } -// NormalizeMultiaddr returns a multiaddr suitable for equality checks. -// If the multiaddr is a webtransport component, it removes the certhashes. -func (h *BasicHost) NormalizeMultiaddr(addr ma.Multiaddr) ma.Multiaddr { - ok, n := libp2pwebtransport.IsWebtransportMultiaddr(addr) - if !ok { - ok, n = libp2pwebrtc.IsWebRTCDirectMultiaddr(addr) - } - if ok && n > 0 { - out := addr - for i := 0; i < n; i++ { - out, _ = ma.SplitLast(out) - } - return out - } - return addr -} - // AllAddrs returns all the addresses the host is listening on except circuit addresses. func (h *BasicHost) AllAddrs() []ma.Multiaddr { return h.addressManager.DirectAddrs() diff --git a/p2p/host/basic/basic_host_test.go b/p2p/host/basic/basic_host_test.go index f44daa07b9..1680db2f2f 100644 --- a/p2p/host/basic/basic_host_test.go +++ b/p2p/host/basic/basic_host_test.go @@ -869,14 +869,6 @@ func peerRecordFromEnvelope(t *testing.T, ev *record.Envelope) *peer.PeerRecord return peerRec } -func TestNormalizeMultiaddr(t *testing.T) { - h1, err := NewHost(swarmt.GenSwarm(t), nil) - require.NoError(t, err) - defer h1.Close() - - require.Equal(t, "/ip4/1.2.3.4/udp/9999/quic-v1/webtransport", h1.NormalizeMultiaddr(ma.StringCast("/ip4/1.2.3.4/udp/9999/quic-v1/webtransport/certhash/uEgNmb28")).String()) -} - func TestTrimHostAddrList(t *testing.T) { type testCase struct { name string diff --git a/p2p/protocol/identify/obsaddr.go b/p2p/host/basic/obsaddr.go similarity index 61% rename from p2p/protocol/identify/obsaddr.go rename to p2p/host/basic/obsaddr.go index 06e54bf5fd..91bffa9724 100644 --- a/p2p/protocol/identify/obsaddr.go +++ b/p2p/host/basic/obsaddr.go @@ -1,8 +1,8 @@ -package identify +package basichost import ( "context" - "fmt" + "errors" "net" "slices" "sort" @@ -32,38 +32,16 @@ type thinWaist struct { Addr, TW, Rest ma.Multiaddr } -// thinWaistWithCount is a thinWaist along with the count of the connection that have it as the local address -type thinWaistWithCount struct { - thinWaist - Count int -} +var errTW = errors.New("not a thinwaist address") func thinWaistForm(a ma.Multiaddr) (thinWaist, error) { - i := 0 - tw, rest := ma.SplitFunc(a, func(c ma.Component) bool { - if i > 1 { - return true - } - switch i { - case 0: - if c.Protocol().Code == ma.P_IP4 || c.Protocol().Code == ma.P_IP6 { - i++ - return false - } - return true - case 1: - if c.Protocol().Code == ma.P_TCP || c.Protocol().Code == ma.P_UDP { - i++ - return false - } - return true - } - return false - }) - if i <= 1 { - return thinWaist{}, fmt.Errorf("not a thinwaist address: %s", a) + if len(a) < 2 { + return thinWaist{}, errTW + } + if c0, c1 := a[0].Code(), a[1].Code(); (c0 != ma.P_IP4 && c0 != ma.P_IP6) || (c1 != ma.P_TCP && c1 != ma.P_UDP) { + return thinWaist{}, errTW } - return thinWaist{Addr: a, TW: tw, Rest: rest}, nil + return thinWaist{Addr: a, TW: a[:2], Rest: a[2:]}, nil } // getObserver returns the observer for the multiaddress @@ -88,8 +66,8 @@ type connMultiaddrs interface { } // observerSetCacheSize is the number of transport sharing the same thinwaist (tcp, ws, wss), (quic, webtransport, webrtc-direct) -// This is 3 in practice right now, but keep a buffer of 3 extra elements -const observerSetCacheSize = 5 +// This is 3 in practice right now, but keep a buffer of few extra elements +const observerSetCacheSize = 10 // observerSet is the set of observers who have observed ThinWaistAddr type observerSet struct { @@ -138,20 +116,12 @@ type observation struct { observed ma.Multiaddr } -// ObservedAddrManager maps connection's local multiaddrs to their externally observable multiaddress -type ObservedAddrManager struct { +// ObservedAddrsManager maps connection's local multiaddrs to their externally observable multiaddress +type ObservedAddrsManager struct { // Our listen addrs listenAddrs func() []ma.Multiaddr - // Our listen addrs with interface addrs for unspecified addrs - interfaceListenAddrs func() ([]ma.Multiaddr, error) - // All host addrs - hostAddrs func() []ma.Multiaddr - // Any normalization required before comparing. Useful to remove certhash - normalize func(ma.Multiaddr) ma.Multiaddr // worker channel for new observations wch chan observation - // notified on recording an observation - addrRecordedNotif chan struct{} // for closing wg sync.WaitGroup @@ -163,49 +133,40 @@ type ObservedAddrManager struct { externalAddrs map[string]map[string]*observerSet // connObservedTWAddrs maps the connection to the last observed thin waist multiaddr on that connection connObservedTWAddrs map[connMultiaddrs]ma.Multiaddr - // localMultiaddr => thin waist form with the count of the connections the multiaddr - // was seen on for tracking our local listen addresses - localAddrs map[string]*thinWaistWithCount } // NewObservedAddrManager returns a new address manager using peerstore.OwnObservedAddressTTL as the TTL. -func NewObservedAddrManager(listenAddrs, hostAddrs func() []ma.Multiaddr, - interfaceListenAddrs func() ([]ma.Multiaddr, error), normalize func(ma.Multiaddr) ma.Multiaddr) (*ObservedAddrManager, error) { - if normalize == nil { - normalize = func(addr ma.Multiaddr) ma.Multiaddr { return addr } - } - o := &ObservedAddrManager{ - externalAddrs: make(map[string]map[string]*observerSet), - connObservedTWAddrs: make(map[connMultiaddrs]ma.Multiaddr), - localAddrs: make(map[string]*thinWaistWithCount), - wch: make(chan observation, observedAddrManagerWorkerChannelSize), - addrRecordedNotif: make(chan struct{}, 1), - listenAddrs: listenAddrs, - interfaceListenAddrs: interfaceListenAddrs, - hostAddrs: hostAddrs, - normalize: normalize, +func NewObservedAddrManager(listenAddrs func() []ma.Multiaddr) (*ObservedAddrsManager, error) { + o := &ObservedAddrsManager{ + externalAddrs: make(map[string]map[string]*observerSet), + connObservedTWAddrs: make(map[connMultiaddrs]ma.Multiaddr), + wch: make(chan observation, observedAddrManagerWorkerChannelSize), + listenAddrs: listenAddrs, } o.ctx, o.ctxCancel = context.WithCancel(context.Background()) + return o, nil +} + +func (o *ObservedAddrsManager) Start() { o.wg.Add(1) go o.worker() - return o, nil } // AddrsFor return all activated observed addresses associated with the given // (resolved) listen address. -func (o *ObservedAddrManager) AddrsFor(addr ma.Multiaddr) (addrs []ma.Multiaddr) { +func (o *ObservedAddrsManager) AddrsFor(addr ma.Multiaddr) (addrs []ma.Multiaddr) { if addr == nil { return nil } o.mu.RLock() defer o.mu.RUnlock() - tw, err := thinWaistForm(o.normalize(addr)) + tw, err := thinWaistForm(addr) if err != nil { return nil } - observerSets := o.getTopExternalAddrs(string(tw.TW.Bytes())) + observerSets := o.getTopExternalAddrs(string(tw.TW.Bytes()), ActivationThresh) res := make([]ma.Multiaddr, 0, len(observerSets)) for _, s := range observerSets { res = append(res, s.cacheMultiaddr(tw.Rest)) @@ -213,37 +174,27 @@ func (o *ObservedAddrManager) AddrsFor(addr ma.Multiaddr) (addrs []ma.Multiaddr) return res } -// appendInferredAddrs infers the external address of other transports that -// share the local thin waist with a transport that we have do observations for. +// appendInferredAddrs infers the external address of addresses for the addresses +// that we are listening on using the thin waist mapping. // // e.g. If we have observations for a QUIC address on port 9000, and we are // listening on the same interface and port 9000 for WebTransport, we can infer // the external WebTransport address. -func (o *ObservedAddrManager) appendInferredAddrs(twToObserverSets map[string][]*observerSet, addrs []ma.Multiaddr) []ma.Multiaddr { +func (o *ObservedAddrsManager) appendInferredAddrs(twToObserverSets map[string][]*observerSet, addrs []ma.Multiaddr) []ma.Multiaddr { if twToObserverSets == nil { twToObserverSets = make(map[string][]*observerSet) for localTWStr := range o.externalAddrs { - twToObserverSets[localTWStr] = append(twToObserverSets[localTWStr], o.getTopExternalAddrs(localTWStr)...) + twToObserverSets[localTWStr] = append(twToObserverSets[localTWStr], o.getTopExternalAddrs(localTWStr, ActivationThresh)...) } } - lAddrs, err := o.interfaceListenAddrs() - if err != nil { - log.Warnw("failed to get interface resolved listen addrs. Using just the listen addrs", "error", err) - lAddrs = nil - } - lAddrs = append(lAddrs, o.listenAddrs()...) + lAddrs := o.listenAddrs() seenTWs := make(map[string]struct{}) for _, a := range lAddrs { - if _, ok := o.localAddrs[string(a.Bytes())]; ok { - // We already have this address in the list - continue - } if _, ok := seenTWs[string(a.Bytes())]; ok { // We've already added this continue } seenTWs[string(a.Bytes())] = struct{}{} - a = o.normalize(a) t, err := thinWaistForm(a) if err != nil { continue @@ -255,30 +206,29 @@ func (o *ObservedAddrManager) appendInferredAddrs(twToObserverSets map[string][] return addrs } -// Addrs return all activated observed addresses -func (o *ObservedAddrManager) Addrs() []ma.Multiaddr { +// Addrs return all observed addresses with at least minObservers observers +// If minObservers <= 0, it will return all addresses with at least ActivationThresh observers. +func (o *ObservedAddrsManager) Addrs(minObservers int) []ma.Multiaddr { o.mu.RLock() defer o.mu.RUnlock() + if minObservers <= 0 { + minObservers = ActivationThresh + } + m := make(map[string][]*observerSet) for localTWStr := range o.externalAddrs { - m[localTWStr] = append(m[localTWStr], o.getTopExternalAddrs(localTWStr)...) + m[localTWStr] = append(m[localTWStr], o.getTopExternalAddrs(localTWStr, minObservers)...) } addrs := make([]ma.Multiaddr, 0, maxExternalThinWaistAddrsPerLocalAddr*5) // assume 5 transports - for _, t := range o.localAddrs { - for _, s := range m[string(t.TW.Bytes())] { - addrs = append(addrs, s.cacheMultiaddr(t.Rest)) - } - } - addrs = o.appendInferredAddrs(m, addrs) return addrs } -func (o *ObservedAddrManager) getTopExternalAddrs(localTWStr string) []*observerSet { +func (o *ObservedAddrsManager) getTopExternalAddrs(localTWStr string, minObservers int) []*observerSet { observerSets := make([]*observerSet, 0, len(o.externalAddrs[localTWStr])) for _, v := range o.externalAddrs[localTWStr] { - if len(v.ObservedBy) >= ActivationThresh { + if len(v.ObservedBy) >= minObservers { observerSets = append(observerSets, v) } } @@ -289,17 +239,10 @@ func (o *ObservedAddrManager) getTopExternalAddrs(localTWStr string) []*observer } // In case we have elements with equal counts, // keep the address list stable by using the lexicographically smaller address - as := a.ObservedTWAddr.String() - bs := b.ObservedTWAddr.String() - if as < bs { - return -1 - } else if as > bs { - return 1 - } else { - return 0 - } - + return a.ObservedTWAddr.Compare(b.ObservedTWAddr) }) + // TODO(sukunrt): Improve this logic. Return only if the addresses have a + // threshold fraction of the maximum observations n := len(observerSets) if n > maxExternalThinWaistAddrsPerLocalAddr { n = maxExternalThinWaistAddrsPerLocalAddr @@ -308,7 +251,7 @@ func (o *ObservedAddrManager) getTopExternalAddrs(localTWStr string) []*observer } // Record enqueues an observation for recording -func (o *ObservedAddrManager) Record(conn connMultiaddrs, observed ma.Multiaddr) { +func (o *ObservedAddrsManager) Record(conn connMultiaddrs, observed ma.Multiaddr) { select { case o.wch <- observation{ conn: conn, @@ -322,9 +265,8 @@ func (o *ObservedAddrManager) Record(conn connMultiaddrs, observed ma.Multiaddr) } } -func (o *ObservedAddrManager) worker() { +func (o *ObservedAddrsManager) worker() { defer o.wg.Done() - for { select { case obs := <-o.wch: @@ -336,11 +278,15 @@ func (o *ObservedAddrManager) worker() { } func isRelayedAddress(a ma.Multiaddr) bool { - _, err := a.ValueForProtocol(ma.P_CIRCUIT) - return err == nil + for _, c := range a { + if c.Code() == ma.P_CIRCUIT { + return true + } + } + return false } -func (o *ObservedAddrManager) shouldRecordObservation(conn connMultiaddrs, observed ma.Multiaddr) (shouldRecord bool, localTW thinWaist, observedTW thinWaist) { +func (o *ObservedAddrsManager) shouldRecordObservation(conn connMultiaddrs, observed ma.Multiaddr) (shouldRecord bool, localTW thinWaist, observedTW thinWaist) { if conn == nil || observed == nil { return false, thinWaist{}, thinWaist{} } @@ -361,62 +307,39 @@ func (o *ObservedAddrManager) shouldRecordObservation(conn connMultiaddrs, obser return false, thinWaist{}, thinWaist{} } - // we should only use ObservedAddr when our connection's LocalAddr is one - // of our ListenAddrs. If we Dial out using an ephemeral addr, knowing that - // address's external mapping is not very useful because the port will not be - // the same as the listen addr. - ifaceaddrs, err := o.interfaceListenAddrs() + localTW, err := thinWaistForm(conn.LocalMultiaddr()) if err != nil { - log.Infof("failed to get interface listen addrs", err) return false, thinWaist{}, thinWaist{} } - for i, a := range ifaceaddrs { - ifaceaddrs[i] = o.normalize(a) - } - - local := o.normalize(conn.LocalMultiaddr()) - listenAddrs := o.listenAddrs() for i, a := range listenAddrs { - listenAddrs[i] = o.normalize(a) + tw, err := thinWaistForm(a) + if err != nil { + listenAddrs[i] = nil + continue + } + listenAddrs[i] = tw.TW } - if !ma.Contains(ifaceaddrs, local) && !ma.Contains(listenAddrs, local) { + if !ma.Contains(listenAddrs, localTW.TW) { // not in our list return false, thinWaist{}, thinWaist{} } - localTW, err = thinWaistForm(local) - if err != nil { - return false, thinWaist{}, thinWaist{} - } - observedTW, err = thinWaistForm(o.normalize(observed)) + observedTW, err = thinWaistForm(observed) if err != nil { return false, thinWaist{}, thinWaist{} } - - hostAddrs := o.hostAddrs() - for i, a := range hostAddrs { - hostAddrs[i] = o.normalize(a) - } - - // We should reject the connection if the observation doesn't match the - // transports of one of our advertised addresses. - if !HasConsistentTransport(observed, hostAddrs) && - !HasConsistentTransport(observed, listenAddrs) { - log.Debugw( - "observed multiaddr doesn't match the transports of any announced addresses", - "from", conn.RemoteMultiaddr(), - "observed", observed, - ) - return false, thinWaist{}, thinWaist{} + if !hasConsistentTransport(localTW.TW, observedTW.TW) { + log.Debugf("invalid observed address %s for local address %s", observed, localTW.Addr) + return } return true, localTW, observedTW } -func (o *ObservedAddrManager) maybeRecordObservation(conn connMultiaddrs, observed ma.Multiaddr) { +func (o *ObservedAddrsManager) maybeRecordObservation(conn connMultiaddrs, observed ma.Multiaddr) { shouldRecord, localTW, observedTW := o.shouldRecordObservation(conn, observed) if !shouldRecord { return @@ -426,13 +349,9 @@ func (o *ObservedAddrManager) maybeRecordObservation(conn connMultiaddrs, observ o.mu.Lock() defer o.mu.Unlock() o.recordObservationUnlocked(conn, localTW, observedTW) - select { - case o.addrRecordedNotif <- struct{}{}: - default: - } } -func (o *ObservedAddrManager) recordObservationUnlocked(conn connMultiaddrs, localTW, observedTW thinWaist) { +func (o *ObservedAddrsManager) recordObservationUnlocked(conn connMultiaddrs, localTW, observedTW thinWaist) { if conn.IsClosed() { // dont record if the connection is already closed. Any previous observations will be removed in // the disconnected callback @@ -446,29 +365,20 @@ func (o *ObservedAddrManager) recordObservationUnlocked(conn connMultiaddrs, loc } prevObservedTWAddr, ok := o.connObservedTWAddrs[conn] - if !ok { - t, ok := o.localAddrs[string(localTW.Addr.Bytes())] - if !ok { - t = &thinWaistWithCount{ - thinWaist: localTW, - } - o.localAddrs[string(localTW.Addr.Bytes())] = t - } - t.Count++ - } else { + if ok { if prevObservedTWAddr.Equal(observedTW.TW) { // we have received the same observation again, nothing to do return + } else { + // if we have a previous entry remove it from externalAddrs + o.removeExternalAddrsUnlocked(observer, localTWStr, string(prevObservedTWAddr.Bytes())) } - // if we have a previous entry remove it from externalAddrs - o.removeExternalAddrsUnlocked(observer, localTWStr, string(prevObservedTWAddr.Bytes())) - // no need to change the localAddrs map here } o.connObservedTWAddrs[conn] = observedTW.TW o.addExternalAddrsUnlocked(observedTW.TW, observer, localTWStr, observedTWStr) } -func (o *ObservedAddrManager) removeExternalAddrsUnlocked(observer, localTWStr, observedTWStr string) { +func (o *ObservedAddrsManager) removeExternalAddrsUnlocked(observer, localTWStr, observedTWStr string) { s, ok := o.externalAddrs[localTWStr][observedTWStr] if !ok { return @@ -485,7 +395,7 @@ func (o *ObservedAddrManager) removeExternalAddrsUnlocked(observer, localTWStr, } } -func (o *ObservedAddrManager) addExternalAddrsUnlocked(observedTWAddr ma.Multiaddr, observer, localTWStr, observedTWStr string) { +func (o *ObservedAddrsManager) addExternalAddrsUnlocked(observedTWAddr ma.Multiaddr, observer, localTWStr, observedTWStr string) { s, ok := o.externalAddrs[localTWStr][observedTWStr] if !ok { s = &observerSet{ @@ -500,7 +410,7 @@ func (o *ObservedAddrManager) addExternalAddrsUnlocked(observedTWAddr ma.Multiad s.ObservedBy[observer]++ } -func (o *ObservedAddrManager) removeConn(conn connMultiaddrs) { +func (o *ObservedAddrsManager) RemoveConn(conn connMultiaddrs) { if conn == nil { return } @@ -513,20 +423,10 @@ func (o *ObservedAddrManager) removeConn(conn connMultiaddrs) { } delete(o.connObservedTWAddrs, conn) - // normalize before obtaining the thinWaist so that we are always dealing - // with the normalized form of the address - localTW, err := thinWaistForm(o.normalize(conn.LocalMultiaddr())) + localTW, err := thinWaistForm(conn.LocalMultiaddr()) if err != nil { return } - t, ok := o.localAddrs[string(localTW.Addr.Bytes())] - if !ok { - return - } - t.Count-- - if t.Count <= 0 { - delete(o.localAddrs, string(localTW.Addr.Bytes())) - } observer, err := getObserver(conn.RemoteMultiaddr()) if err != nil { @@ -534,13 +434,9 @@ func (o *ObservedAddrManager) removeConn(conn connMultiaddrs) { } o.removeExternalAddrsUnlocked(observer, string(localTW.TW.Bytes()), string(observedTWAddr.Bytes())) - select { - case o.addrRecordedNotif <- struct{}{}: - default: - } } -func (o *ObservedAddrManager) getNATType() (tcpNATType, udpNATType network.NATDeviceType) { +func (o *ObservedAddrsManager) getNATType() (tcpNATType, udpNATType network.NATDeviceType) { o.mu.RLock() defer o.mu.RUnlock() @@ -549,10 +445,12 @@ func (o *ObservedAddrManager) getNATType() (tcpNATType, udpNATType network.NATDe for _, m := range o.externalAddrs { isTCP := false for _, v := range m { - if _, err := v.ObservedTWAddr.ValueForProtocol(ma.P_TCP); err == nil { - isTCP = true + for _, c := range v.ObservedTWAddr { + if c.Code() == ma.P_TCP { + isTCP = true + break + } } - break } for _, v := range m { if isTCP { @@ -578,6 +476,9 @@ func (o *ObservedAddrManager) getNATType() (tcpNATType, udpNATType network.NATDe // If the top elements cover more than 1/2 of all the observations, there's a > 50% chance that // hole punching based on outputs of observed address manager will succeed + // + // The `3*maxExternalThinWaistAddrsPerLocalAddr` is a magic number, we just want sufficient + // observations to decide about NAT Type if tcpTotal >= 3*maxExternalThinWaistAddrsPerLocalAddr { if tcpTopCounts >= tcpTotal/2 { tcpNATType = network.NATDeviceTypeCone @@ -595,8 +496,22 @@ func (o *ObservedAddrManager) getNATType() (tcpNATType, udpNATType network.NATDe return } -func (o *ObservedAddrManager) Close() error { +func (o *ObservedAddrsManager) Close() error { o.ctxCancel() o.wg.Wait() return nil } + +// hasConsistentTransport returns true if the thin waist address `aTW` shares the same +// protocols with `bTW` +func hasConsistentTransport(aTW, bTW ma.Multiaddr) bool { + if len(aTW) != len(bTW) { + return false + } + for i, a := range aTW { + if bTW[i].Code() != a.Code() { + return false + } + } + return true +} diff --git a/p2p/protocol/identify/obsaddr_glass_test.go b/p2p/host/basic/obsaddr_glass_test.go similarity index 75% rename from p2p/protocol/identify/obsaddr_glass_test.go rename to p2p/host/basic/obsaddr_glass_test.go index d596be9ba8..e175fa52e4 100644 --- a/p2p/protocol/identify/obsaddr_glass_test.go +++ b/p2p/host/basic/obsaddr_glass_test.go @@ -1,4 +1,4 @@ -package identify +package basichost // This test lives in the identify package, not the identify_test package, so it // can access internal types. @@ -38,17 +38,14 @@ func (c *mockConn) IsClosed() bool { func TestShouldRecordObservationWithWebTransport(t *testing.T) { listenAddr := ma.StringCast("/ip4/0.0.0.0/udp/0/quic-v1/webtransport/certhash/uEgNmb28") - ifaceAddr := ma.StringCast("/ip4/10.0.0.2/udp/9999/quic-v1/webtransport/certhash/uEgNmb28") listenAddrs := func() []ma.Multiaddr { return []ma.Multiaddr{listenAddr} } - ifaceListenAddrs := func() ([]ma.Multiaddr, error) { return []ma.Multiaddr{ifaceAddr}, nil } - addrs := func() []ma.Multiaddr { return []ma.Multiaddr{listenAddr} } c := &mockConn{ local: listenAddr, remote: ma.StringCast("/ip4/1.2.3.6/udp/1236/quic-v1/webtransport"), } observedAddr := ma.StringCast("/ip4/1.2.3.4/udp/1231/quic-v1/webtransport") - o, err := NewObservedAddrManager(listenAddrs, addrs, ifaceListenAddrs, normalize) + o, err := NewObservedAddrManager(listenAddrs) require.NoError(t, err) shouldRecord, _, _ := o.shouldRecordObservation(c, observedAddr) require.True(t, shouldRecord) @@ -56,17 +53,14 @@ func TestShouldRecordObservationWithWebTransport(t *testing.T) { func TestShouldNotRecordObservationWithRelayedAddr(t *testing.T) { listenAddr := ma.StringCast("/ip4/1.2.3.4/udp/8888/quic-v1/p2p-circuit") - ifaceAddr := ma.StringCast("/ip4/10.0.0.2/udp/9999/quic-v1") listenAddrs := func() []ma.Multiaddr { return []ma.Multiaddr{listenAddr} } - ifaceListenAddrs := func() ([]ma.Multiaddr, error) { return []ma.Multiaddr{ifaceAddr}, nil } - addrs := func() []ma.Multiaddr { return []ma.Multiaddr{listenAddr} } c := &mockConn{ local: listenAddr, remote: ma.StringCast("/ip4/1.2.3.6/udp/1236/quic-v1/p2p-circuit"), } observedAddr := ma.StringCast("/ip4/1.2.3.4/udp/1231/quic-v1/p2p-circuit") - o, err := NewObservedAddrManager(listenAddrs, addrs, ifaceListenAddrs, normalize) + o, err := NewObservedAddrManager(listenAddrs) require.NoError(t, err) shouldRecord, _, _ := o.shouldRecordObservation(c, observedAddr) require.False(t, shouldRecord) @@ -74,48 +68,48 @@ func TestShouldNotRecordObservationWithRelayedAddr(t *testing.T) { func TestShouldRecordObservationWithNAT64Addr(t *testing.T) { listenAddr1 := ma.StringCast("/ip4/0.0.0.0/tcp/1234") - ifaceAddr1 := ma.StringCast("/ip4/10.0.0.2/tcp/4321") listenAddr2 := ma.StringCast("/ip6/::/tcp/1234") - ifaceAddr2 := ma.StringCast("/ip6/1::1/tcp/4321") - - var ( - listenAddrs = func() []ma.Multiaddr { return []ma.Multiaddr{listenAddr1, listenAddr2} } - ifaceListenAddrs = func() ([]ma.Multiaddr, error) { return []ma.Multiaddr{ifaceAddr1, ifaceAddr2}, nil } - addrs = func() []ma.Multiaddr { return []ma.Multiaddr{listenAddr1, listenAddr2} } - ) - c := &mockConn{ + listenAddrs := func() []ma.Multiaddr { return []ma.Multiaddr{listenAddr1, listenAddr2} } + c4 := &mockConn{ local: listenAddr1, remote: ma.StringCast("/ip4/1.2.3.6/tcp/4321"), } + c6 := &mockConn{ + local: listenAddr2, + remote: ma.StringCast("/ip6/1::4/tcp/4321"), + } cases := []struct { addr ma.Multiaddr want bool + conn *mockConn failureReason string }{ { addr: ma.StringCast("/ip4/1.2.3.4/tcp/1234"), want: true, failureReason: "IPv4 should be observed", + conn: c4, }, { addr: ma.StringCast("/ip6/1::4/tcp/1234"), want: true, failureReason: "public IPv6 address should be observed", + conn: c6, }, { addr: ma.StringCast("/ip6/64:ff9b::192.0.1.2/tcp/1234"), want: false, failureReason: "NAT64 IPv6 address shouldn't be observed", + conn: c6, }, } - o, err := NewObservedAddrManager(listenAddrs, addrs, ifaceListenAddrs, normalize) + o, err := NewObservedAddrManager(listenAddrs) require.NoError(t, err) for i, tc := range cases { t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { - - if shouldRecord, _, _ := o.shouldRecordObservation(c, tc.addr); shouldRecord != tc.want { + if shouldRecord, _, _ := o.shouldRecordObservation(tc.conn, tc.addr); shouldRecord != tc.want { t.Fatalf("%s %s", tc.addr, tc.failureReason) } }) diff --git a/p2p/protocol/identify/obsaddr_test.go b/p2p/host/basic/obsaddr_test.go similarity index 65% rename from p2p/protocol/identify/obsaddr_test.go rename to p2p/host/basic/obsaddr_test.go index f2261c012f..644722f03f 100644 --- a/p2p/protocol/identify/obsaddr_test.go +++ b/p2p/host/basic/obsaddr_test.go @@ -1,4 +1,4 @@ -package identify +package basichost import ( crand "crypto/rand" @@ -8,11 +8,7 @@ import ( "testing" "time" - "github.com/libp2p/go-libp2p/core/event" "github.com/libp2p/go-libp2p/core/network" - blankhost "github.com/libp2p/go-libp2p/p2p/host/blank" - "github.com/libp2p/go-libp2p/p2p/host/eventbus" - swarmt "github.com/libp2p/go-libp2p/p2p/net/swarm/testing" ma "github.com/multiformats/go-multiaddr" matest "github.com/multiformats/go-multiaddr/matest" @@ -44,62 +40,62 @@ func newConn(local, remote ma.Multiaddr) *mockConn { return &mockConn{local: local, remote: remote} } -func normalize(addr ma.Multiaddr) ma.Multiaddr { - for { - out, last := ma.SplitLast(addr) - if last == nil { - return addr - } - if _, err := last.ValueForProtocol(ma.P_CERTHASH); err != nil { - return addr - } - addr = out - } -} - -// subtractFrom takes the difference between two slices of multiaddrs (a - b) -func subtractFrom(a, b []ma.Multiaddr) []string { - bSet := make(map[string]struct{}, len(b)) - for _, addr := range b { - bSet[string(addr.Bytes())] = struct{}{} - } - out := make([]string, 0, len(a)) - for _, addr := range a { - if _, ok := bSet[string(addr.Bytes())]; !ok { - out = append(out, addr.String()) - } - } - return out -} - -func TestObservedAddrManager(t *testing.T) { +func TestObservedAddrsManager(t *testing.T) { tcp4ListenAddr := ma.StringCast("/ip4/192.168.1.100/tcp/1") quic4ListenAddr := ma.StringCast("/ip4/0.0.0.0/udp/1/quic-v1") webTransport4ListenAddr := ma.StringCast("/ip4/0.0.0.0/udp/1/quic-v1/webtransport/certhash/uEgNmb28") tcp6ListenAddr := ma.StringCast("/ip6/2004::1/tcp/1") quic6ListenAddr := ma.StringCast("/ip6/::/udp/1/quic-v1") webTransport6ListenAddr := ma.StringCast("/ip6/::/udp/1/quic-v1/webtransport/certhash/uEgNmb28") - newObservedAddrMgr := func() *ObservedAddrManager { - listenAddrs := []ma.Multiaddr{ - tcp4ListenAddr, quic4ListenAddr, webTransport4ListenAddr, tcp6ListenAddr, quic6ListenAddr, webTransport6ListenAddr, - } + newObservedAddrMgr := func() *ObservedAddrsManager { listenAddrsFunc := func() []ma.Multiaddr { - return listenAddrs - } - interfaceListenAddrsFunc := func() ([]ma.Multiaddr, error) { - return listenAddrs, nil + return []ma.Multiaddr{ + tcp4ListenAddr, quic4ListenAddr, webTransport4ListenAddr, tcp6ListenAddr, quic6ListenAddr, webTransport6ListenAddr, + } } - o, err := NewObservedAddrManager(listenAddrsFunc, listenAddrsFunc, - interfaceListenAddrsFunc, normalize) + o, err := NewObservedAddrManager(listenAddrsFunc) if err != nil { t.Fatal(err) } + o.Start() + t.Cleanup(func() { o.Close() }) return o } - checkAllEntriesRemoved := func(o *ObservedAddrManager) bool { - return len(o.Addrs()) == 0 && len(o.externalAddrs) == 0 && len(o.connObservedTWAddrs) == 0 && len(o.localAddrs) == 0 + checkAllEntriesRemoved := func(o *ObservedAddrsManager) bool { + return len(o.Addrs(0)) == 0 && len(o.externalAddrs) == 0 && len(o.connObservedTWAddrs) == 0 } + + getConns := func(t *testing.T, n int, protocolCode int) []*mockConn { + t.Helper() + localAddrMap := map[int]ma.Multiaddr{ + ma.P_TCP: tcp4ListenAddr, + ma.P_QUIC_V1: quic4ListenAddr, + ma.P_WEBTRANSPORT: webTransport4ListenAddr, + } + protoPartMap := map[int]ma.Multiaddr{ + ma.P_TCP: ma.StringCast("/tcp/1"), + ma.P_QUIC_V1: ma.StringCast("/udp/1/quic-v1"), + ma.P_WEBTRANSPORT: ma.StringCast("/udp/1/quic-v1/webtransport"), + } + + localAddr, ok := localAddrMap[protocolCode] + if !ok { + t.Fatalf("unknown protocol code: %d", protocolCode) + } + protoPart, ok := protoPartMap[protocolCode] + if !ok { + t.Fatalf("unknown protocol code: %d", protocolCode) + } + + conns := make([]*mockConn, 0, n) + for i := 0; i < n; i++ { + ipPart := ma.StringCast(fmt.Sprintf("/ip4/1.2.3.%d", i)) + conns = append(conns, newConn(localAddr, ma.Join(ipPart, protoPart))) + } + return conns + } + t.Run("Single Observation", func(t *testing.T) { o := newObservedAddrMgr() defer o.Close() @@ -113,12 +109,36 @@ func TestObservedAddrManager(t *testing.T) { o.Record(c3, observed) o.Record(c4, observed) require.Eventually(t, func() bool { - return matest.AssertEqualMultiaddrs(t, o.Addrs(), []ma.Multiaddr{observed}) + return matest.AssertEqualMultiaddrs(t, o.Addrs(0), []ma.Multiaddr{observed}) }, 1*time.Second, 100*time.Millisecond) - o.removeConn(c1) - o.removeConn(c2) - o.removeConn(c3) - o.removeConn(c4) + o.RemoveConn(c1) + o.RemoveConn(c2) + o.RemoveConn(c3) + o.RemoveConn(c4) + require.Eventually(t, func() bool { + return checkAllEntriesRemoved(o) + }, 1*time.Second, 100*time.Millisecond) + }) + + t.Run("many observed addrs output size limited", func(t *testing.T) { + o := newObservedAddrMgr() + defer o.Close() + conns := getConns(t, 40, ma.P_TCP) + observedAddrs := make([]ma.Multiaddr, maxExternalThinWaistAddrsPerLocalAddr*2) + for i := 0; i < len(observedAddrs); i++ { + observedAddrs[i] = ma.StringCast(fmt.Sprintf("/ip4/2.2.2.%d/tcp/2", i)) + } + for i, c := range conns { + // avoid the async nature of Record + o.maybeRecordObservation(c, observedAddrs[i%len(observedAddrs)]) + } + require.Eventually(t, func() bool { + return len(o.Addrs(ActivationThresh)) == maxExternalThinWaistAddrsPerLocalAddr && + len(o.AddrsFor(tcp4ListenAddr)) == maxExternalThinWaistAddrsPerLocalAddr + }, 1*time.Second, 100*time.Millisecond) + for _, c := range conns { + o.RemoveConn(c) + } require.Eventually(t, func() bool { return checkAllEntriesRemoved(o) }, 1*time.Second, 100*time.Millisecond) @@ -128,7 +148,7 @@ func TestObservedAddrManager(t *testing.T) { o := newObservedAddrMgr() defer o.Close() observedQuic := ma.StringCast("/ip4/2.2.2.2/udp/2/quic-v1") - observedWebTransport := ma.StringCast("/ip4/2.2.2.2/udp/2/quic-v1/webtransport") + observedWebTransport := ma.StringCast("/ip4/2.2.2.2/udp/2/quic-v1/webtransport/certhash/uEgNmb28") c1 := newConn(quic4ListenAddr, ma.StringCast("/ip4/1.2.3.1/udp/1/quic-v1")) c2 := newConn(quic4ListenAddr, ma.StringCast("/ip4/1.2.3.2/udp/1/quic-v1")) c3 := newConn(webTransport4ListenAddr, ma.StringCast("/ip4/1.2.3.3/udp/1/quic-v1/webtransport")) @@ -138,13 +158,12 @@ func TestObservedAddrManager(t *testing.T) { o.Record(c3, observedWebTransport) o.Record(c4, observedWebTransport) require.EventuallyWithT(t, func(t *assert.CollectT) { - matest.AssertEqualMultiaddrs(t, o.Addrs(), []ma.Multiaddr{observedQuic, observedWebTransport}) - matest.AssertEqualMultiaddrs(t, o.appendInferredAddrs(nil, nil), []ma.Multiaddr{}) + matest.AssertEqualMultiaddrs(t, o.Addrs(0), []ma.Multiaddr{observedQuic, observedWebTransport}) }, 1*time.Second, 100*time.Millisecond) - o.removeConn(c1) - o.removeConn(c2) - o.removeConn(c3) - o.removeConn(c4) + o.RemoveConn(c1) + o.RemoveConn(c2) + o.RemoveConn(c3) + o.RemoveConn(c4) require.Eventually(t, func() bool { return checkAllEntriesRemoved(o) }, 1*time.Second, 100*time.Millisecond) @@ -154,7 +173,7 @@ func TestObservedAddrManager(t *testing.T) { o := newObservedAddrMgr() defer o.Close() observedQuic := ma.StringCast("/ip4/2.2.2.2/udp/2/quic-v1") - inferredWebTransport := ma.StringCast("/ip4/2.2.2.2/udp/2/quic-v1/webtransport") + inferredWebTransport := ma.StringCast("/ip4/2.2.2.2/udp/2/quic-v1/webtransport/certhash/uEgNmb28") c1 := newConn(quic4ListenAddr, ma.StringCast("/ip4/1.2.3.1/udp/1/quic-v1")) c2 := newConn(quic4ListenAddr, ma.StringCast("/ip4/1.2.3.2/udp/1/quic-v1")) c3 := newConn(quic4ListenAddr, ma.StringCast("/ip4/1.2.3.3/udp/1/quic-v1")) @@ -164,13 +183,12 @@ func TestObservedAddrManager(t *testing.T) { o.Record(c3, observedQuic) o.Record(c4, observedQuic) require.EventuallyWithT(t, func(t *assert.CollectT) { - matest.AssertEqualMultiaddrs(t, o.Addrs(), []ma.Multiaddr{observedQuic, inferredWebTransport}) - matest.AssertEqualMultiaddrs(t, o.appendInferredAddrs(nil, nil), []ma.Multiaddr{inferredWebTransport}) + matest.AssertEqualMultiaddrs(t, o.Addrs(0), []ma.Multiaddr{observedQuic, inferredWebTransport}) }, 1*time.Second, 100*time.Millisecond) - o.removeConn(c1) - o.removeConn(c2) - o.removeConn(c3) - o.removeConn(c4) + o.RemoveConn(c1) + o.RemoveConn(c2) + o.RemoveConn(c3) + o.RemoveConn(c4) require.Eventually(t, func() bool { return checkAllEntriesRemoved(o) }, 1*time.Second, 100*time.Millisecond) @@ -181,7 +199,7 @@ func TestObservedAddrManager(t *testing.T) { defer o.Close() observedQuic := ma.StringCast("/ip4/2.2.2.2/udp/2/quic-v1") - inferredWebTransport := ma.StringCast("/ip4/2.2.2.2/udp/2/quic-v1/webtransport") + inferredWebTransport := ma.StringCast("/ip4/2.2.2.2/udp/2/quic-v1/webtransport/certhash/uEgNmb28") const N = 4 // ActivationThresh var ob1, ob2 [N]connMultiaddrs @@ -194,28 +212,27 @@ func TestObservedAddrManager(t *testing.T) { o.Record(ob2[i], observedQuic) } time.Sleep(100 * time.Millisecond) - require.Equal(t, o.Addrs(), []ma.Multiaddr{}) + require.Equal(t, o.Addrs(0), []ma.Multiaddr{}) // We should have a valid address now o.Record(ob1[N-1], observedQuic) o.Record(ob2[N-1], observedQuic) require.EventuallyWithT(t, func(t *assert.CollectT) { - matest.AssertEqualMultiaddrs(t, o.Addrs(), []ma.Multiaddr{observedQuic, inferredWebTransport}) - matest.AssertEqualMultiaddrs(t, o.appendInferredAddrs(nil, nil), []ma.Multiaddr{inferredWebTransport}) + matest.AssertEqualMultiaddrs(t, o.Addrs(0), []ma.Multiaddr{observedQuic, inferredWebTransport}) }, 2*time.Second, 100*time.Millisecond) // Now disconnect first observer group for i := 0; i < N; i++ { - o.removeConn(ob1[i]) + o.RemoveConn(ob1[i]) } time.Sleep(100 * time.Millisecond) - if !matest.AssertEqualMultiaddrs(t, o.Addrs(), []ma.Multiaddr{observedQuic, inferredWebTransport}) { - t.Fatalf("address removed too earyl %v %v", o.Addrs(), observedQuic) + if !matest.AssertEqualMultiaddrs(t, o.Addrs(0), []ma.Multiaddr{observedQuic, inferredWebTransport}) { + t.Fatalf("address removed too earyl %v %v", o.Addrs(0), observedQuic) } // Now disconnect the second group to check cleanup for i := 0; i < N; i++ { - o.removeConn(ob2[i]) + o.RemoveConn(ob2[i]) } require.Eventually(t, func() bool { return checkAllEntriesRemoved(o) @@ -228,8 +245,8 @@ func TestObservedAddrManager(t *testing.T) { observedQuic1 := ma.StringCast("/ip4/2.2.2.2/udp/2/quic-v1") observedQuic2 := ma.StringCast("/ip4/2.2.2.2/udp/3/quic-v1") - inferredWebTransport1 := ma.StringCast("/ip4/2.2.2.2/udp/2/quic-v1/webtransport") - inferredWebTransport2 := ma.StringCast("/ip4/2.2.2.2/udp/3/quic-v1/webtransport") + inferredWebTransport1 := ma.StringCast("/ip4/2.2.2.2/udp/2/quic-v1/webtransport/certhash/uEgNmb28") + inferredWebTransport2 := ma.StringCast("/ip4/2.2.2.2/udp/3/quic-v1/webtransport/certhash/uEgNmb28") const N = 4 // ActivationThresh var ob1, ob2 [N]connMultiaddrs @@ -242,28 +259,27 @@ func TestObservedAddrManager(t *testing.T) { o.Record(ob2[i], observedQuic2) } time.Sleep(100 * time.Millisecond) - require.Equal(t, o.Addrs(), []ma.Multiaddr{}) + require.Equal(t, o.Addrs(0), []ma.Multiaddr{}) // We should have a valid address now o.Record(ob1[N-1], observedQuic1) o.Record(ob2[N-1], observedQuic2) require.EventuallyWithT(t, func(t *assert.CollectT) { - matest.AssertEqualMultiaddrs(t, o.Addrs(), []ma.Multiaddr{observedQuic1, observedQuic2, inferredWebTransport1, inferredWebTransport2}) - matest.AssertEqualMultiaddrs(t, o.appendInferredAddrs(nil, nil), []ma.Multiaddr{inferredWebTransport1, inferredWebTransport2}) + matest.AssertEqualMultiaddrs(t, o.Addrs(0), []ma.Multiaddr{observedQuic1, observedQuic2, inferredWebTransport1, inferredWebTransport2}) }, 2*time.Second, 100*time.Millisecond) // Now disconnect first observer group for i := 0; i < N; i++ { - o.removeConn(ob1[i]) + o.RemoveConn(ob1[i]) } time.Sleep(100 * time.Millisecond) - if !matest.AssertEqualMultiaddrs(t, o.Addrs(), []ma.Multiaddr{observedQuic2, inferredWebTransport2}) { - t.Fatalf("address removed too early %v %v", o.Addrs(), observedQuic2) + if !matest.AssertEqualMultiaddrs(t, o.Addrs(0), []ma.Multiaddr{observedQuic2, inferredWebTransport2}) { + t.Fatalf("address removed too early %v %v", o.Addrs(0), observedQuic2) } // Now disconnect the second group to check cleanup for i := 0; i < N; i++ { - o.removeConn(ob2[i]) + o.RemoveConn(ob2[i]) } require.Eventually(t, func() bool { return checkAllEntriesRemoved(o) @@ -279,11 +295,12 @@ func TestObservedAddrManager(t *testing.T) { c4 := newConn(webTransport4ListenAddr, ma.StringCast("/ip4/1.2.3.4/udp/1/quic-v1/webtransport")) c5 := newConn(quic4ListenAddr, ma.StringCast("/ip4/1.2.3.5/udp/1/quic-v1")) c6 := newConn(quic4ListenAddr, ma.StringCast("/ip4/1.2.3.6/udp/1/quic-v1")) - var observedQuic, observedWebTransport ma.Multiaddr + var observedQuic, observedWebTransport, observedWebTransportWithCertHash ma.Multiaddr for i := 0; i < 10; i++ { // Change the IP address in each observation observedQuic = ma.StringCast(fmt.Sprintf("/ip4/2.2.2.%d/udp/2/quic-v1", i)) observedWebTransport = ma.StringCast(fmt.Sprintf("/ip4/2.2.2.%d/udp/2/quic-v1/webtransport", i)) + observedWebTransportWithCertHash = ma.StringCast(fmt.Sprintf("/ip4/2.2.2.%d/udp/2/quic-v1/webtransport/certhash/uEgNmb28", i)) o.Record(c1, observedQuic) o.Record(c2, observedQuic) o.Record(c3, observedWebTransport) @@ -293,93 +310,30 @@ func TestObservedAddrManager(t *testing.T) { } require.EventuallyWithT(t, func(t *assert.CollectT) { - matest.AssertEqualMultiaddrs(t, o.Addrs(), []ma.Multiaddr{observedQuic, observedWebTransport}) + matest.AssertEqualMultiaddrs(t, o.Addrs(0), []ma.Multiaddr{observedQuic, observedWebTransportWithCertHash}) }, 1*time.Second, 100*time.Millisecond) tw, err := thinWaistForm(quic4ListenAddr) require.NoError(t, err) require.Less(t, len(o.externalAddrs[string(tw.TW.Bytes())]), 2) - requireEqualAddrs(t, []ma.Multiaddr{observedWebTransport}, o.AddrsFor(webTransport4ListenAddr)) + requireEqualAddrs(t, []ma.Multiaddr{observedWebTransportWithCertHash}, o.AddrsFor(webTransport4ListenAddr)) requireEqualAddrs(t, []ma.Multiaddr{observedQuic}, o.AddrsFor(quic4ListenAddr)) - requireAddrsMatch(t, []ma.Multiaddr{observedQuic, observedWebTransport}, o.Addrs()) + requireAddrsMatch(t, []ma.Multiaddr{observedQuic, observedWebTransportWithCertHash}, o.Addrs(0)) for i := 0; i < 3; i++ { // remove non-recorded connection - o.removeConn(c6) + o.RemoveConn(c6) } - requireEqualAddrs(t, []ma.Multiaddr{observedWebTransport}, o.AddrsFor(webTransport4ListenAddr)) + requireEqualAddrs(t, []ma.Multiaddr{observedWebTransportWithCertHash}, o.AddrsFor(webTransport4ListenAddr)) requireEqualAddrs(t, []ma.Multiaddr{observedQuic}, o.AddrsFor(quic4ListenAddr)) - requireAddrsMatch(t, []ma.Multiaddr{observedQuic, observedWebTransport}, o.Addrs()) + requireAddrsMatch(t, []ma.Multiaddr{observedQuic, observedWebTransportWithCertHash}, o.Addrs(0)) - o.removeConn(c1) - o.removeConn(c2) - o.removeConn(c3) - o.removeConn(c4) - o.removeConn(c5) - require.Eventually(t, func() bool { - return checkAllEntriesRemoved(o) - }, 1*time.Second, 100*time.Millisecond) - }) - - t.Run("Many connection many observations", func(t *testing.T) { - o := newObservedAddrMgr() - defer o.Close() - const N = 100 - var tcpConns, quicConns, webTransportConns [N]*mockConn - for i := 0; i < N; i++ { - tcpConns[i] = newConn(tcp4ListenAddr, ma.StringCast(fmt.Sprintf("/ip4/1.2.3.%d/tcp/1", i))) - quicConns[i] = newConn(quic4ListenAddr, ma.StringCast(fmt.Sprintf("/ip4/1.2.3.%d/udp/1/quic-v1", i))) - webTransportConns[i] = newConn(webTransport4ListenAddr, ma.StringCast(fmt.Sprintf("/ip4/1.2.3.%d/udp/1/quic-v1/webtransport", i))) - } - var observedQuic, observedWebTransport, observedTCP ma.Multiaddr - for i := 0; i < N; i++ { - for j := 0; j < 5; j++ { - // ip addr has the form 2.2.. - observedQuic = ma.StringCast(fmt.Sprintf("/ip4/2.2.%d.%d/udp/2/quic-v1", i/10, j)) - observedWebTransport = ma.StringCast(fmt.Sprintf("/ip4/2.2.%d.%d/udp/2/quic-v1/webtransport", i/10, j)) - observedTCP = ma.StringCast(fmt.Sprintf("/ip4/2.2.%d.%d/tcp/2", i/10, j)) - o.Record(tcpConns[i], observedTCP) - o.Record(quicConns[i], observedQuic) - o.Record(webTransportConns[i], observedWebTransport) - time.Sleep(10 * time.Millisecond) - } - } - // At this point we have 10 groups of N / 10 with 10 observations for every connection - // The output should remain stable - require.Eventually(t, func() bool { - return len(o.Addrs()) == 3*maxExternalThinWaistAddrsPerLocalAddr - }, 1*time.Second, 100*time.Millisecond) - addrs := o.Addrs() - for i := 0; i < 10; i++ { - require.ElementsMatch(t, o.Addrs(), addrs, "%s %s", o.Addrs(), addrs) - time.Sleep(10 * time.Millisecond) - } - - // Now we bias a few address counts and check for sorting correctness - var resTCPAddrs, resQuicAddrs, resWebTransportAddrs [maxExternalThinWaistAddrsPerLocalAddr]ma.Multiaddr - for i := 0; i < maxExternalThinWaistAddrsPerLocalAddr; i++ { - resTCPAddrs[i] = ma.StringCast(fmt.Sprintf("/ip4/2.2.%d.4/tcp/2", 9-i)) - resQuicAddrs[i] = ma.StringCast(fmt.Sprintf("/ip4/2.2.%d.4/udp/2/quic-v1", 9-i)) - resWebTransportAddrs[i] = ma.StringCast(fmt.Sprintf("/ip4/2.2.%d.4/udp/2/quic-v1/webtransport", 9-i)) - o.Record(tcpConns[i], resTCPAddrs[i]) - o.Record(quicConns[i], resQuicAddrs[i]) - o.Record(webTransportConns[i], resWebTransportAddrs[i]) - time.Sleep(10 * time.Millisecond) - } - var allAddrs []ma.Multiaddr - allAddrs = append(allAddrs, resTCPAddrs[:]...) - allAddrs = append(allAddrs, resQuicAddrs[:]...) - allAddrs = append(allAddrs, resWebTransportAddrs[:]...) - require.EventuallyWithT(t, func(t *assert.CollectT) { - matest.AssertMultiaddrsMatch(t, o.Addrs(), allAddrs) - }, 1*time.Second, 100*time.Millisecond) - - for i := 0; i < N; i++ { - o.removeConn(tcpConns[i]) - o.removeConn(quicConns[i]) - o.removeConn(webTransportConns[i]) - } + o.RemoveConn(c1) + o.RemoveConn(c2) + o.RemoveConn(c3) + o.RemoveConn(c4) + o.RemoveConn(c5) require.Eventually(t, func() bool { return checkAllEntriesRemoved(o) }, 1*time.Second, 100*time.Millisecond) @@ -388,6 +342,7 @@ func TestObservedAddrManager(t *testing.T) { t.Run("WebTransport certhash", func(t *testing.T) { o := newObservedAddrMgr() observedWebTransport := ma.StringCast("/ip4/2.2.2.2/udp/1/quic-v1/webtransport") + observedWebTransportWithCerthash := ma.StringCast("/ip4/2.2.2.2/udp/1/quic-v1/webtransport/certhash/uEgNmb28") inferredQUIC := ma.StringCast("/ip4/2.2.2.2/udp/1/quic-v1") c1 := newConn(webTransport4ListenAddr, ma.StringCast("/ip4/1.2.3.1/udp/1/quic-v1/webtransport")) c2 := newConn(webTransport4ListenAddr, ma.StringCast("/ip4/1.2.3.2/udp/1/quic-v1/webtransport")) @@ -398,12 +353,12 @@ func TestObservedAddrManager(t *testing.T) { o.Record(c3, observedWebTransport) o.Record(c4, observedWebTransport) require.EventuallyWithT(t, func(t *assert.CollectT) { - matest.AssertEqualMultiaddrs(t, o.Addrs(), []ma.Multiaddr{observedWebTransport, inferredQUIC}) + matest.AssertMultiaddrsMatch(t, o.Addrs(0), []ma.Multiaddr{observedWebTransportWithCerthash, inferredQUIC}) }, 1*time.Second, 100*time.Millisecond) - o.removeConn(c1) - o.removeConn(c2) - o.removeConn(c3) - o.removeConn(c4) + o.RemoveConn(c1) + o.RemoveConn(c2) + o.RemoveConn(c3) + o.RemoveConn(c4) require.Eventually(t, func() bool { return checkAllEntriesRemoved(o) }, 1*time.Second, 100*time.Millisecond) @@ -414,7 +369,6 @@ func TestObservedAddrManager(t *testing.T) { defer o.Close() observedWebTransport := ma.StringCast("/ip4/2.2.2.2/udp/1/quic-v1/webtransport") - inferredQUIC := ma.StringCast("/ip4/2.2.2.2/udp/1/quic-v1") var udpConns [5 * maxExternalThinWaistAddrsPerLocalAddr]connMultiaddrs for i := 0; i < len(udpConns); i++ { udpConns[i] = newConn(webTransport4ListenAddr, ma.StringCast(fmt.Sprintf("/ip4/1.2.3.%d/udp/1/quic-v1/webtransport", i))) @@ -422,13 +376,11 @@ func TestObservedAddrManager(t *testing.T) { time.Sleep(10 * time.Millisecond) } require.EventuallyWithT(t, func(t *assert.CollectT) { - matest.AssertEqualMultiaddrs(t, o.Addrs(), []ma.Multiaddr{observedWebTransport, inferredQUIC}) - matest.AssertEqualMultiaddrs(t, o.appendInferredAddrs(nil, nil), []ma.Multiaddr{inferredQUIC}) + tcpNAT, udpNAT := o.getNATType() + require.Equal(t, tcpNAT, network.NATDeviceTypeUnknown) + require.Equal(t, udpNAT, network.NATDeviceTypeCone) }, 1*time.Second, 100*time.Millisecond) - tcpNAT, udpNAT := o.getNATType() - require.Equal(t, tcpNAT, network.NATDeviceTypeUnknown) - require.Equal(t, udpNAT, network.NATDeviceTypeCone) }) t.Run("NATTypeSymmetric", func(t *testing.T) { o := newObservedAddrMgr() @@ -451,16 +403,21 @@ func TestObservedAddrManager(t *testing.T) { // At this point we have 20 groups with 5 observations for every connection // The output should remain stable require.EventuallyWithT(t, func(t *assert.CollectT) { - require.Equal(t, len(subtractFrom(o.Addrs(), o.appendInferredAddrs(nil, nil))), 2*maxExternalThinWaistAddrsPerLocalAddr) + require.Equal(t, len(o.Addrs(0)), 3*maxExternalThinWaistAddrsPerLocalAddr) }, 1*time.Second, 100*time.Millisecond) + addrs := o.Addrs(0) + for i := 0; i < 10; i++ { + require.ElementsMatch(t, o.Addrs(0), addrs, "%s %s", o.Addrs(0), addrs) + time.Sleep(50 * time.Millisecond) + } tcpNAT, udpNAT := o.getNATType() require.Equal(t, tcpNAT, network.NATDeviceTypeSymmetric) require.Equal(t, udpNAT, network.NATDeviceTypeSymmetric) for i := 0; i < N; i++ { - o.removeConn(tcpConns[i]) - o.removeConn(quicConns[i]) + o.RemoveConn(tcpConns[i]) + o.RemoveConn(quicConns[i]) } require.Eventually(t, func() bool { return checkAllEntriesRemoved(o) @@ -474,52 +431,9 @@ func TestObservedAddrManager(t *testing.T) { o.maybeRecordObservation(newConn(tcp4ListenAddr, remoteAddr), nil) o.maybeRecordObservation(nil, remoteAddr) o.AddrsFor(nil) - o.removeConn(nil) + o.RemoveConn(nil) }) - t.Run("Nat Emitter", func(t *testing.T) { - o := newObservedAddrMgr() - defer o.Close() - bus := eventbus.NewBus() - - s := swarmt.GenSwarm(t, swarmt.EventBus(bus)) - h := blankhost.NewBlankHost(s, blankhost.WithEventBus(bus)) - defer h.Close() - // make reachability private - emitter, err := bus.Emitter(new(event.EvtLocalReachabilityChanged), eventbus.Stateful) - require.NoError(t, err) - emitter.Emit(event.EvtLocalReachabilityChanged{Reachability: network.ReachabilityPrivate}) - - // start nat emitter - n, err := newNATEmitter(h, o, 10*time.Millisecond) - require.NoError(t, err) - defer n.Close() - - sub, err := bus.Subscribe(new(event.EvtNATDeviceTypeChanged)) - require.NoError(t, err) - observedWebTransport := ma.StringCast("/ip4/2.2.2.2/udp/1/quic-v1/webtransport") - inferredQUIC := ma.StringCast("/ip4/2.2.2.2/udp/1/quic-v1") - var udpConns [5 * maxExternalThinWaistAddrsPerLocalAddr]connMultiaddrs - for i := 0; i < len(udpConns); i++ { - udpConns[i] = newConn(webTransport4ListenAddr, ma.StringCast(fmt.Sprintf("/ip4/1.2.3.%d/udp/1/quic-v1/webtransport", i))) - o.Record(udpConns[i], observedWebTransport) - time.Sleep(10 * time.Millisecond) - } - require.EventuallyWithT(t, func(t *assert.CollectT) { - matest.AssertEqualMultiaddrs(t, o.Addrs(), []ma.Multiaddr{observedWebTransport, inferredQUIC}) - matest.AssertEqualMultiaddrs(t, o.appendInferredAddrs(nil, nil), []ma.Multiaddr{inferredQUIC}) - }, 1*time.Second, 100*time.Millisecond) - - var e interface{} - select { - case e = <-sub.Out(): - case <-time.After(2 * time.Second): - t.Fatalf("expected NAT change event") - } - evt := e.(event.EvtNATDeviceTypeChanged) - require.Equal(t, evt.TransportProtocol, network.NATTransportUDP) - require.Equal(t, evt.NatDeviceType, network.NATDeviceTypeCone) - }) t.Run("Many connection many observations IP4 And IP6", func(t *testing.T) { o := newObservedAddrMgr() defer o.Close() @@ -561,21 +475,22 @@ func TestObservedAddrManager(t *testing.T) { // At this point we have 10 groups of N / 10 with 10 observations for every connection // The output should remain stable require.Eventually(t, func() bool { - return len(o.Addrs()) == 2*3*maxExternalThinWaistAddrsPerLocalAddr + return len(o.Addrs(0)) == 2*3*maxExternalThinWaistAddrsPerLocalAddr }, 1*time.Second, 100*time.Millisecond) - addrs := o.Addrs() + addrs := o.Addrs(0) for i := 0; i < 10; i++ { - require.ElementsMatch(t, o.Addrs(), addrs, "%s %s", o.Addrs(), addrs) + require.ElementsMatch(t, o.Addrs(0), addrs, "%s %s", o.Addrs(0), addrs) time.Sleep(10 * time.Millisecond) } // Now we bias a few address counts and check for sorting correctness - var resTCPAddrs, resQuicAddrs, resWebTransportAddrs []ma.Multiaddr + var resTCPAddrs, resQuicAddrs, resWebTransportAddrs, resWebTransportWithCertHashAddrs []ma.Multiaddr for i, idx := 0, 0; i < maxExternalThinWaistAddrsPerLocalAddr; i++ { resTCPAddrs = append(resTCPAddrs, ma.StringCast(fmt.Sprintf("/ip4/2.2.%d.4/tcp/2", 9-i))) resQuicAddrs = append(resQuicAddrs, ma.StringCast(fmt.Sprintf("/ip4/2.2.%d.4/udp/2/quic-v1", 9-i))) resWebTransportAddrs = append(resWebTransportAddrs, ma.StringCast(fmt.Sprintf("/ip4/2.2.%d.4/udp/2/quic-v1/webtransport", 9-i))) + resWebTransportWithCertHashAddrs = append(resWebTransportWithCertHashAddrs, ma.StringCast(fmt.Sprintf("/ip4/2.2.%d.4/udp/2/quic-v1/webtransport/certhash/uEgNmb28", 9-i))) o.maybeRecordObservation(tcp4Conns[i], resTCPAddrs[idx]) o.maybeRecordObservation(quic4Conns[i], resQuicAddrs[idx]) @@ -585,6 +500,8 @@ func TestObservedAddrManager(t *testing.T) { resTCPAddrs = append(resTCPAddrs, ma.StringCast(fmt.Sprintf("/ip6/20%02x::04/tcp/2", 9-i))) resQuicAddrs = append(resQuicAddrs, ma.StringCast(fmt.Sprintf("/ip6/20%02x::04/udp/2/quic-v1", 9-i))) resWebTransportAddrs = append(resWebTransportAddrs, ma.StringCast(fmt.Sprintf("/ip6/20%02x::04/udp/2/quic-v1/webtransport", 9-i))) + resWebTransportWithCertHashAddrs = append(resWebTransportWithCertHashAddrs, ma.StringCast(fmt.Sprintf("/ip6/20%02x::04/udp/2/quic-v1/webtransport/certhash/uEgNmb28", 9-i))) + o.maybeRecordObservation(tcp6Conns[i], resTCPAddrs[idx]) o.maybeRecordObservation(quic6Conns[i], resQuicAddrs[idx]) o.maybeRecordObservation(webTransport6Conns[i], resWebTransportAddrs[idx]) @@ -593,18 +510,18 @@ func TestObservedAddrManager(t *testing.T) { var allAddrs []ma.Multiaddr allAddrs = append(allAddrs, resTCPAddrs[:]...) allAddrs = append(allAddrs, resQuicAddrs[:]...) - allAddrs = append(allAddrs, resWebTransportAddrs[:]...) + allAddrs = append(allAddrs, resWebTransportWithCertHashAddrs[:]...) require.EventuallyWithT(t, func(t *assert.CollectT) { - matest.AssertMultiaddrsMatch(t, o.Addrs(), allAddrs) + matest.AssertMultiaddrsMatch(t, o.Addrs(0), allAddrs) }, 1*time.Second, 100*time.Millisecond) for i := 0; i < N; i++ { - o.removeConn(tcp4Conns[i]) - o.removeConn(quic4Conns[i]) - o.removeConn(webTransport4Conns[i]) - o.removeConn(tcp6Conns[i]) - o.removeConn(quic6Conns[i]) - o.removeConn(webTransport6Conns[i]) + o.RemoveConn(tcp4Conns[i]) + o.RemoveConn(quic4Conns[i]) + o.RemoveConn(webTransport4Conns[i]) + o.RemoveConn(tcp6Conns[i]) + o.RemoveConn(quic6Conns[i]) + o.RemoveConn(webTransport6Conns[i]) } require.Eventually(t, func() bool { return checkAllEntriesRemoved(o) @@ -625,7 +542,7 @@ func genIPMultiaddr(ip6 bool) ma.Multiaddr { return addr } -func FuzzObservedAddrManager(f *testing.F) { +func FuzzObservedAddrsManager(f *testing.F) { protos := []string{ "/webrtc-direct", "/quic-v1", @@ -637,18 +554,14 @@ func FuzzObservedAddrManager(f *testing.F) { tcp6 := ma.StringCast("/ip6/1::1/tcp/1") quic6 := ma.StringCast("/ip6/::/udp/1/quic-v1") wt6 := ma.StringCast("/ip6/::/udp/1/quic-v1/webtransport/certhash/uEgNmb28") - newObservedAddrMgr := func() *ObservedAddrManager { + newObservedAddrMgr := func() *ObservedAddrsManager { listenAddrs := []ma.Multiaddr{ tcp4, quic4, wt4, tcp6, quic6, wt6, } listenAddrsFunc := func() []ma.Multiaddr { return listenAddrs } - interfaceListenAddrsFunc := func() ([]ma.Multiaddr, error) { - return listenAddrs, nil - } - o, err := NewObservedAddrManager(listenAddrsFunc, listenAddrsFunc, - interfaceListenAddrsFunc, normalize) + o, err := NewObservedAddrManager(listenAddrsFunc) if err != nil { panic(err) } @@ -680,7 +593,7 @@ func FuzzObservedAddrManager(f *testing.F) { o.maybeRecordObservation(c, addrs[i]) o.maybeRecordObservation(c, nil) o.maybeRecordObservation(nil, addrs[i]) - o.removeConn(c) + o.RemoveConn(c) } } }) diff --git a/p2p/host/blank/blank.go b/p2p/host/blank/blank.go index 0cf6642f69..e87d27161e 100644 --- a/p2p/host/blank/blank.go +++ b/p2p/host/blank/blank.go @@ -24,20 +24,24 @@ import ( var log = logging.Logger("blankhost") -// BlankHost is the thinnest implementation of the host.Host interface +// BlankHost is a thin implementation of the host.Host interface type BlankHost struct { - n network.Network - mux *mstream.MultistreamMuxer[protocol.ID] - cmgr connmgr.ConnManager - eventbus event.Bus - emitters struct { + N network.Network + M *mstream.MultistreamMuxer[protocol.ID] + E event.Bus + ConnMgr connmgr.ConnManager + // SkipInitSignedRecord is a flag to skip the initialization of a signed record for the host + SkipInitSignedRecord bool + emitters struct { evtLocalProtocolsUpdated event.Emitter } + onStop []func() error } type config struct { - cmgr connmgr.ConnManager - eventBus event.Bus + cmgr connmgr.ConnManager + eventBus event.Bus + skipInitSignedRecord bool } type Option = func(cfg *config) @@ -54,6 +58,12 @@ func WithEventBus(eventBus event.Bus) Option { } } +func SkipInitSignedRecord(eventBus event.Bus) Option { + return func(cfg *config) { + cfg.skipInitSignedRecord = true + } +} + func NewBlankHost(n network.Network, options ...Option) *BlankHost { cfg := config{ cmgr: &connmgr.NullConnMgr{}, @@ -63,36 +73,72 @@ func NewBlankHost(n network.Network, options ...Option) *BlankHost { } bh := &BlankHost{ - n: n, - cmgr: cfg.cmgr, - mux: mstream.NewMultistreamMuxer[protocol.ID](), - eventbus: cfg.eventBus, + N: n, + ConnMgr: cfg.cmgr, + M: mstream.NewMultistreamMuxer[protocol.ID](), + E: cfg.eventBus, + + SkipInitSignedRecord: cfg.skipInitSignedRecord, + } + + if err := bh.Start(); err != nil { + log.Errorf("error creating blank host, err=%s", err) + return nil } - if bh.eventbus == nil { - bh.eventbus = eventbus.NewBus(eventbus.WithMetricsTracer(eventbus.NewMetricsTracer())) + + return bh +} + +func (bh *BlankHost) Start() error { + if bh.E == nil { + bh.E = eventbus.NewBus(eventbus.WithMetricsTracer(eventbus.NewMetricsTracer())) } // subscribe the connection manager to network notifications (has no effect with NullConnMgr) - n.Notify(bh.cmgr.Notifee()) + notifee := bh.ConnMgr.Notifee() + bh.N.Notify(notifee) + bh.onStop = append(bh.onStop, func() error { + bh.N.StopNotify(notifee) + return nil + }) var err error - if bh.emitters.evtLocalProtocolsUpdated, err = bh.eventbus.Emitter(&event.EvtLocalProtocolsUpdated{}); err != nil { - return nil + if bh.emitters.evtLocalProtocolsUpdated, err = bh.E.Emitter(&event.EvtLocalProtocolsUpdated{}); err != nil { + return err } + bh.onStop = append(bh.onStop, func() error { + bh.emitters.evtLocalProtocolsUpdated.Close() + return nil + }) - n.SetStreamHandler(bh.newStreamHandler) + bh.N.SetStreamHandler(bh.newStreamHandler) + bh.onStop = append(bh.onStop, func() error { + bh.N.SetStreamHandler(func(s network.Stream) { s.Reset() }) + return nil + }) // persist a signed peer record for self to the peerstore. - if err := bh.initSignedRecord(); err != nil { - log.Errorf("error creating blank host, err=%s", err) - return nil + if !bh.SkipInitSignedRecord { + if err := bh.initSignedRecord(); err != nil { + log.Errorf("error creating blank host, err=%s", err) + return err + } } - return bh + return nil +} + +func (bh *BlankHost) Stop() error { + var err error + for _, f := range bh.onStop { + err = errors.Join(err, f()) + } + bh.onStop = nil + return err } func (bh *BlankHost) initSignedRecord() error { - cab, ok := peerstore.GetCertifiedAddrBook(bh.n.Peerstore()) + cab, ok := peerstore.GetCertifiedAddrBook(bh.N.Peerstore()) if !ok { log.Error("peerstore does not support signed records") return errors.New("peerstore does not support signed records") @@ -114,7 +160,7 @@ func (bh *BlankHost) initSignedRecord() error { var _ host.Host = (*BlankHost)(nil) func (bh *BlankHost) Addrs() []ma.Multiaddr { - addrs, err := bh.n.InterfaceListenAddresses() + addrs, err := bh.N.InterfaceListenAddresses() if err != nil { log.Debug("error retrieving network interface addrs: ", err) return nil @@ -124,14 +170,14 @@ func (bh *BlankHost) Addrs() []ma.Multiaddr { } func (bh *BlankHost) Close() error { - return bh.n.Close() + return bh.N.Close() } func (bh *BlankHost) Connect(ctx context.Context, ai peer.AddrInfo) error { // absorb addresses into peerstore bh.Peerstore().AddAddrs(ai.ID, ai.Addrs, peerstore.TempAddrTTL) - cs := bh.n.ConnsToPeer(ai.ID) + cs := bh.N.ConnsToPeer(ai.ID) if len(cs) > 0 { return nil } @@ -144,15 +190,15 @@ func (bh *BlankHost) Connect(ctx context.Context, ai peer.AddrInfo) error { } func (bh *BlankHost) Peerstore() peerstore.Peerstore { - return bh.n.Peerstore() + return bh.N.Peerstore() } func (bh *BlankHost) ID() peer.ID { - return bh.n.LocalPeer() + return bh.N.LocalPeer() } func (bh *BlankHost) NewStream(ctx context.Context, p peer.ID, protos ...protocol.ID) (network.Stream, error) { - s, err := bh.n.NewStream(ctx, p) + s, err := bh.N.NewStream(ctx, p) if err != nil { return nil, fmt.Errorf("failed to open stream: %w", err) } @@ -216,18 +262,18 @@ func (bh *BlankHost) newStreamHandler(s network.Stream) { // TODO: i'm not sure this really needs to be here func (bh *BlankHost) Mux() protocol.Switch { - return bh.mux + return bh.M } // TODO: also not sure this fits... Might be better ways around this (leaky abstractions) func (bh *BlankHost) Network() network.Network { - return bh.n + return bh.N } func (bh *BlankHost) ConnManager() connmgr.ConnManager { - return bh.cmgr + return bh.ConnMgr } func (bh *BlankHost) EventBus() event.Bus { - return bh.eventbus + return bh.E } diff --git a/p2p/net/swarm/swarm.go b/p2p/net/swarm/swarm.go index 9fff13f8e2..95c83ec8d7 100644 --- a/p2p/net/swarm/swarm.go +++ b/p2p/net/swarm/swarm.go @@ -961,3 +961,31 @@ func (r ResolverFromMaDNS) ResolveDNSComponent(ctx context.Context, maddr ma.Mul } return addrs, nil } + +// AddCertHashes adds certificate hashes to relevant transport addresses, if there +// are no certhashes already present on the method. It mutates `listenAddrs`. +// This method is useful for adding certhashes to public addresses discovered +// via identify, nat mapping, or provided by the user. +func (s *Swarm) AddCertHashes(listenAddrs []ma.Multiaddr) []ma.Multiaddr { + type addCertHasher interface { + AddCertHashes(m ma.Multiaddr) (ma.Multiaddr, bool) + } + + for i, addr := range listenAddrs { + t := s.TransportForListening(addr) + if t == nil { + continue + } + tpt, ok := t.(addCertHasher) + if !ok { + continue + } + addrWithCerthash, added := tpt.AddCertHashes(addr) + if !added { + log.Warnf("Couldn't add certhashes to multiaddr: %s", addr) + continue + } + listenAddrs[i] = addrWithCerthash + } + return listenAddrs +} diff --git a/p2p/net/swarm/swarm_test.go b/p2p/net/swarm/swarm_test.go index c911d5ee8b..57b8137ea2 100644 --- a/p2p/net/swarm/swarm_test.go +++ b/p2p/net/swarm/swarm_test.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "io" + "slices" "strings" "sync" "testing" @@ -567,3 +568,61 @@ func TestListenCloseCount(t *testing.T) { _, err := remainingAddrs[0].ValueForProtocol(ma.P_TCP) require.NoError(t, err, "expected the TCP address to still be present") } + +func TestAddCertHashes(t *testing.T) { + s := GenSwarm(t) + + listenAddrs := s.ListenAddresses() + splitCertHashes := func(a ma.Multiaddr) (prefix, certhashes ma.Multiaddr, ok bool) { + for i, c := range a { + if c.Protocol().Code == ma.P_CERTHASH { + return prefix, a[i:], true + } + prefix = append(prefix, c) + } + return prefix, certhashes, false + } + addrWithNewIPPort := func(addr ma.Multiaddr, newIPPort ma.Multiaddr) ma.Multiaddr { + a := slices.Clone(addr) + a[0] = newIPPort[0] + a[1] = newIPPort[1] + return a + } + publicIPPort := []ma.Multiaddr{ + ma.StringCast("/ip4/1.1.1.1/udp/1"), + ma.StringCast("/ip4/1.2.3.4/udp/1"), + ma.StringCast("/ip6/2005::/udp/1"), + } + + certHashComponent := ma.StringCast("/certhash/uEgNmb28") + for _, a := range listenAddrs { + prefix, certhashes, ok := splitCertHashes(a) + if !ok { + continue + } + var publicAddrs []ma.Multiaddr + for _, tc := range publicIPPort { + publicAddrs = append(publicAddrs, addrWithNewIPPort(prefix, tc)) + } + finalAddrs := s.AddCertHashes(publicAddrs) + for _, a := range finalAddrs { + _, certhash2, ok := splitCertHashes(a) + require.True(t, ok) + require.Equal(t, certhashes, certhash2) + } + + // if the addr has a certhash already, check it isn't modified + publicAddrs = nil + for _, tc := range publicIPPort { + a := addrWithNewIPPort(prefix, tc) + a = append(a, certHashComponent...) + publicAddrs = append(publicAddrs, a) + } + finalAddrs = s.AddCertHashes(publicAddrs) + for _, a := range finalAddrs { + _, certhash2, ok := splitCertHashes(a) + require.True(t, ok) + require.Equal(t, certHashComponent, certhash2) + } + } +} diff --git a/p2p/protocol/autonatv2/autonat_test.go b/p2p/protocol/autonatv2/autonat_test.go index 097ac98a23..7ef2fa223a 100644 --- a/p2p/protocol/autonatv2/autonat_test.go +++ b/p2p/protocol/autonatv2/autonat_test.go @@ -587,17 +587,7 @@ func TestEventSubscription(t *testing.T) { } 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 - } - }, - } + c := &client{} tests := []struct { name string localAddr ma.Multiaddr @@ -829,3 +819,10 @@ func FuzzClient(f *testing.F) { c.GetReachability(context.Background(), reqs) }) } + +func TestNormalizeMultiaddr(t *testing.T) { + require.Equal(t, + "/ip4/1.2.3.4/udp/9999/quic-v1/webtransport", + normalizeMultiaddr(ma.StringCast("/ip4/1.2.3.4/udp/9999/quic-v1/webtransport/certhash/uEgNmb28")).String(), + ) +} diff --git a/p2p/protocol/autonatv2/client.go b/p2p/protocol/autonatv2/client.go index d0cb02e681..4e2aa120ba 100644 --- a/p2p/protocol/autonatv2/client.go +++ b/p2p/protocol/autonatv2/client.go @@ -21,10 +21,9 @@ import ( // 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 - metricsTracer MetricsTracer + host host.Host + dialData []byte + metricsTracer MetricsTracer mu sync.Mutex // dialBackQueues maps nonce to the channel for providing the local multiaddr of the connection @@ -32,10 +31,6 @@ type client struct { dialBackQueues map[uint64]chan ma.Multiaddr } -type normalizeMultiaddrer interface { - NormalizeMultiaddr(ma.Multiaddr) ma.Multiaddr -} - func newClient(s *autoNATSettings) *client { return &client{ dialData: make([]byte, 4000), @@ -45,12 +40,7 @@ func newClient(s *autoNATSettings) *client { } func (ac *client) Start(h host.Host) { - normalizeMultiaddr := func(a ma.Multiaddr) ma.Multiaddr { return a } - if hn, ok := h.(normalizeMultiaddrer); ok { - normalizeMultiaddr = hn.NormalizeMultiaddr - } ac.host = h - ac.normalizeMultiaddr = normalizeMultiaddr ac.host.SetStreamHandler(DialBackProtocol, ac.handleDialBack) } @@ -325,8 +315,8 @@ func (ac *client) areAddrsConsistent(connLocalAddr, dialedAddr ma.Multiaddr) boo if len(connLocalAddr) == 0 || len(dialedAddr) == 0 { return false } - connLocalAddr = ac.normalizeMultiaddr(connLocalAddr) - dialedAddr = ac.normalizeMultiaddr(dialedAddr) + connLocalAddr = normalizeMultiaddr(connLocalAddr) + dialedAddr = normalizeMultiaddr(dialedAddr) localProtos := connLocalAddr.Protocols() externalProtos := dialedAddr.Protocols() diff --git a/p2p/protocol/autonatv2/client_js.go b/p2p/protocol/autonatv2/client_js.go new file mode 100644 index 0000000000..f4c62f040c --- /dev/null +++ b/p2p/protocol/autonatv2/client_js.go @@ -0,0 +1,24 @@ +//go:build js +// +build js + +package autonatv2 + +import ( + libp2pwebtransport "github.com/libp2p/go-libp2p/p2p/transport/webtransport" + ma "github.com/multiformats/go-multiaddr" +) + +// normalizeMultiaddr returns a multiaddr suitable for equality checks. +// If the multiaddr is a webtransport component, it removes the certhashes. +func normalizeMultiaddr(addr ma.Multiaddr) ma.Multiaddr { + ok, n := libp2pwebtransport.IsWebtransportMultiaddr(addr) + + if ok && n > 0 { + out := addr + for i := 0; i < n; i++ { + out, _ = ma.SplitLast(out) + } + return out + } + return addr +} diff --git a/p2p/protocol/autonatv2/client_native.go b/p2p/protocol/autonatv2/client_native.go new file mode 100644 index 0000000000..9ae6079554 --- /dev/null +++ b/p2p/protocol/autonatv2/client_native.go @@ -0,0 +1,27 @@ +//go:build !js +// +build !js + +package autonatv2 + +import ( + libp2pwebrtc "github.com/libp2p/go-libp2p/p2p/transport/webrtc" + libp2pwebtransport "github.com/libp2p/go-libp2p/p2p/transport/webtransport" + ma "github.com/multiformats/go-multiaddr" +) + +// normalizeMultiaddr returns a multiaddr suitable for equality checks. +// If the multiaddr is a webtransport component, it removes the certhashes. +func normalizeMultiaddr(addr ma.Multiaddr) ma.Multiaddr { + ok, n := libp2pwebtransport.IsWebtransportMultiaddr(addr) + if !ok { + ok, n = libp2pwebrtc.IsWebRTCDirectMultiaddr(addr) + } + if ok && n > 0 { + out := addr + for i := 0; i < n; i++ { + out, _ = ma.SplitLast(out) + } + return out + } + return addr +} diff --git a/p2p/protocol/holepunch/holepunch_test.go b/p2p/protocol/holepunch/holepunch_test.go index 09e46f10ad..8982ca0094 100644 --- a/p2p/protocol/holepunch/holepunch_test.go +++ b/p2p/protocol/holepunch/holepunch_test.go @@ -65,22 +65,12 @@ func (m mockMaddrFilter) FilterRemote(remoteID peer.ID, maddrs []ma.Multiaddr) [ var _ holepunch.AddrFilter = &mockMaddrFilter{} -type mockIDService struct { - identify.IDService -} - -var _ identify.IDService = &mockIDService{} - -func newMockIDService(t *testing.T, h host.Host) identify.IDService { +func newIDService(t *testing.T, h host.Host) identify.IDService { ids, err := identify.NewIDService(h) require.NoError(t, err) ids.Start() t.Cleanup(func() { ids.Close() }) - return &mockIDService{IDService: ids} -} - -func (s *mockIDService) OwnObservedAddrs() []ma.Multiaddr { - return append(s.IDService.OwnObservedAddrs(), ma.StringCast("/ip4/1.1.1.1/tcp/1234")) + return ids } func TestNoHolePunchIfDirectConnExists(t *testing.T) { @@ -655,7 +645,7 @@ func quicSimConn(isPubliclyReachably bool, router *simconn.SimpleFirewallRouter) func addHolePunchService(t *testing.T, h host.Host, extraAddrs []ma.Multiaddr, opts ...holepunch.Option) *holepunch.Service { t.Helper() - hps, err := holepunch.NewService(h, newMockIDService(t, h), func() []ma.Multiaddr { + hps, err := holepunch.NewService(h, newIDService(t, h), func() []ma.Multiaddr { addrs := h.Addrs() addrs = append(addrs, extraAddrs...) return addrs diff --git a/p2p/protocol/identify/id.go b/p2p/protocol/identify/id.go index f4c6f96003..576c6d0071 100644 --- a/p2p/protocol/identify/id.go +++ b/p2p/protocol/identify/id.go @@ -114,11 +114,6 @@ type IDService interface { // identified) and returns a channel that is closed when the identify protocol // completes. IdentifyWait(network.Conn) <-chan struct{} - // OwnObservedAddrs returns the addresses peers have reported we've dialed from - OwnObservedAddrs() []ma.Multiaddr - // ObservedAddrsFor returns the addresses peers have reported we've dialed from, - // for a specific local address. - ObservedAddrsFor(local ma.Multiaddr) []ma.Multiaddr Start() io.Closer } @@ -175,10 +170,6 @@ type idService struct { addrMu sync.Mutex - // our own observed addresses. - observedAddrMgr *ObservedAddrManager - disableObservedAddrManager bool - emitters struct { evtPeerProtocolsUpdated event.Emitter evtPeerIdentificationCompleted event.Emitter @@ -190,15 +181,9 @@ type idService struct { snapshot identifySnapshot } - natEmitter *natEmitter - rateLimiter *rate.Limiter } -type normalizer interface { - NormalizeMultiaddr(ma.Multiaddr) ma.Multiaddr -} - // NewIDService constructs a new *idService and activates it by // attaching its stream handler to the given host.Host. func NewIDService(h host.Host, opts ...Option) (*idService, error) { @@ -237,28 +222,7 @@ func NewIDService(h host.Host, opts ...Option) (*idService, error) { }, } - var normalize func(ma.Multiaddr) ma.Multiaddr - if hn, ok := h.(normalizer); ok { - normalize = hn.NormalizeMultiaddr - } - var err error - if cfg.disableObservedAddrManager { - s.disableObservedAddrManager = true - } else { - observedAddrs, err := NewObservedAddrManager(h.Network().ListenAddresses, - h.Addrs, h.Network().InterfaceListenAddresses, normalize) - if err != nil { - return nil, fmt.Errorf("failed to create observed address manager: %s", err) - } - natEmitter, err := newNATEmitter(h, observedAddrs, time.Minute) - if err != nil { - return nil, fmt.Errorf("failed to create nat emitter: %s", err) - } - s.natEmitter = natEmitter - s.observedAddrMgr = observedAddrs - } - s.emitters.evtPeerProtocolsUpdated, err = h.EventBus().Emitter(&event.EvtPeerProtocolsUpdated{}) if err != nil { log.Warnf("identify service not emitting peer protocol updates; err: %s", err) @@ -396,28 +360,10 @@ func (ids *idService) sendPushes(ctx context.Context) { // Close shuts down the idService func (ids *idService) Close() error { ids.ctxCancel() - if !ids.disableObservedAddrManager { - ids.observedAddrMgr.Close() - ids.natEmitter.Close() - } ids.refCount.Wait() return nil } -func (ids *idService) OwnObservedAddrs() []ma.Multiaddr { - if ids.disableObservedAddrManager { - return nil - } - return ids.observedAddrMgr.Addrs() -} - -func (ids *idService) ObservedAddrsFor(local ma.Multiaddr) []ma.Multiaddr { - if ids.disableObservedAddrManager { - return nil - } - return ids.observedAddrMgr.AddrsFor(local) -} - // IdentifyConn runs the Identify protocol on a connection. // It returns when we've received the peer's Identify message (or the request fails). // If successful, the peer store will contain the peer's addresses and supported protocols. @@ -802,11 +748,6 @@ func (ids *idService) consumeMessage(mes *pb.Identify, c network.Conn, isPush bo obsAddr = nil } - if obsAddr != nil && !ids.disableObservedAddrManager { - // TODO refactor this to use the emitted events instead of having this func call explicitly. - ids.observedAddrMgr.Record(c, obsAddr) - } - // mes.ListenAddrs laddrs := mes.GetListenAddrs() lmaddrs := make([]ma.Multiaddr, 0, len(laddrs)) @@ -1072,10 +1013,6 @@ func (nn *netNotifiee) Disconnected(_ network.Network, c network.Conn) { delete(ids.conns, c) ids.connsMu.Unlock() - if !ids.disableObservedAddrManager { - ids.observedAddrMgr.removeConn(c) - } - // Last disconnect. // Undo the setting of addresses to peer.ConnectedAddrTTL we did ids.addrMu.Lock() diff --git a/p2p/protocol/identify/nat_emitter.go b/p2p/protocol/identify/nat_emitter.go deleted file mode 100644 index fec9b68fe2..0000000000 --- a/p2p/protocol/identify/nat_emitter.go +++ /dev/null @@ -1,119 +0,0 @@ -package identify - -import ( - "context" - "fmt" - "sync" - "time" - - "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/p2p/host/eventbus" -) - -type natEmitter struct { - ctx context.Context - cancel context.CancelFunc - wg sync.WaitGroup - reachabilitySub event.Subscription - reachability network.Reachability - eventInterval time.Duration - - currentUDPNATDeviceType network.NATDeviceType - currentTCPNATDeviceType network.NATDeviceType - emitNATDeviceTypeChanged event.Emitter - - observedAddrMgr *ObservedAddrManager -} - -func newNATEmitter(h host.Host, o *ObservedAddrManager, eventInterval time.Duration) (*natEmitter, error) { - ctx, cancel := context.WithCancel(context.Background()) - n := &natEmitter{ - observedAddrMgr: o, - ctx: ctx, - cancel: cancel, - eventInterval: eventInterval, - } - reachabilitySub, err := h.EventBus().Subscribe(new(event.EvtLocalReachabilityChanged), eventbus.Name("identify (nat emitter)")) - if err != nil { - return nil, fmt.Errorf("failed to subscribe to reachability event: %s", err) - } - n.reachabilitySub = reachabilitySub - - emitter, err := h.EventBus().Emitter(new(event.EvtNATDeviceTypeChanged), eventbus.Stateful) - if err != nil { - return nil, fmt.Errorf("failed to create emitter for NATDeviceType: %s", err) - } - n.emitNATDeviceTypeChanged = emitter - - n.wg.Add(1) - go n.worker() - return n, nil -} - -func (n *natEmitter) worker() { - defer n.wg.Done() - subCh := n.reachabilitySub.Out() - ticker := time.NewTicker(n.eventInterval) - pendingUpdate := false - enoughTimeSinceLastUpdate := true - for { - select { - case evt, ok := <-subCh: - if !ok { - subCh = nil - continue - } - ev, ok := evt.(event.EvtLocalReachabilityChanged) - if !ok { - log.Error("invalid event: %v", evt) - continue - } - n.reachability = ev.Reachability - case <-ticker.C: - enoughTimeSinceLastUpdate = true - if pendingUpdate { - n.maybeNotify() - pendingUpdate = false - enoughTimeSinceLastUpdate = false - } - case <-n.observedAddrMgr.addrRecordedNotif: - pendingUpdate = true - if enoughTimeSinceLastUpdate { - n.maybeNotify() - pendingUpdate = false - enoughTimeSinceLastUpdate = false - } - case <-n.ctx.Done(): - return - } - } -} - -func (n *natEmitter) maybeNotify() { - if n.reachability == network.ReachabilityPrivate { - tcpNATType, udpNATType := n.observedAddrMgr.getNATType() - if tcpNATType != n.currentTCPNATDeviceType { - n.currentTCPNATDeviceType = tcpNATType - n.emitNATDeviceTypeChanged.Emit(event.EvtNATDeviceTypeChanged{ - TransportProtocol: network.NATTransportTCP, - NatDeviceType: n.currentTCPNATDeviceType, - }) - } - if udpNATType != n.currentUDPNATDeviceType { - n.currentUDPNATDeviceType = udpNATType - n.emitNATDeviceTypeChanged.Emit(event.EvtNATDeviceTypeChanged{ - TransportProtocol: network.NATTransportUDP, - NatDeviceType: n.currentUDPNATDeviceType, - }) - } - } -} - -func (n *natEmitter) Close() { - n.cancel() - n.wg.Wait() - n.reachabilitySub.Close() - n.emitNATDeviceTypeChanged.Close() -} diff --git a/p2p/protocol/ping/ping.go b/p2p/protocol/ping/ping.go index a846f40499..c8d72e95d9 100644 --- a/p2p/protocol/ping/ping.go +++ b/p2p/protocol/ping/ping.go @@ -39,6 +39,14 @@ func NewPingService(h host.Host) *PingService { return ps } +func (p *PingService) Start() { + p.Host.SetStreamHandler(ID, p.PingHandler) +} + +func (p *PingService) Stop() { + p.Host.RemoveStreamHandler(ID) +} + func (p *PingService) PingHandler(s network.Stream) { if err := s.Scope().SetService(ServiceName); err != nil { log.Debugf("error attaching stream to ping service: %s", err) diff --git a/p2p/transport/webrtc/transport.go b/p2p/transport/webrtc/transport.go index 8a5fd335e7..09a31a9666 100644 --- a/p2p/transport/webrtc/transport.go +++ b/p2p/transport/webrtc/transport.go @@ -537,7 +537,7 @@ func (t *WebRTCTransport) AddCertHashes(addr ma.Multiaddr) (ma.Multiaddr, bool) if err != nil { return nil, false } - return addr.AppendComponent(certComp), true + return addr.Encapsulate(certComp), true } type netConnWrapper struct {