diff --git a/config/config.go b/config/config.go index 59cdbee576..634384e12e 100644 --- a/config/config.go +++ b/config/config.go @@ -9,6 +9,7 @@ import ( "github.com/libp2p/go-libp2p-core/connmgr" "github.com/libp2p/go-libp2p-core/crypto" "github.com/libp2p/go-libp2p-core/host" + "github.com/libp2p/go-libp2p-core/introspection" "github.com/libp2p/go-libp2p-core/metrics" "github.com/libp2p/go-libp2p-core/network" "github.com/libp2p/go-libp2p-core/peer" @@ -44,6 +45,13 @@ type NATManagerC func(network.Network) bhost.NATManager type RoutingC func(host.Host) (routing.PeerRouting, error) +// IntrospectorC represents an introspector constructor. +type IntrospectorC func(host.Host, metrics.Reporter) (introspection.Introspector, error) + +// IntrospectionEndpointC is a type that represents an introspect.Endpoint +// constructor. +type IntrospectionEndpointC func(introspection.Introspector) (introspection.Endpoint, error) + // AutoNATConfig defines the AutoNAT behavior for the libp2p host. type AutoNATConfig struct { ForceReachability *network.Reachability @@ -92,6 +100,9 @@ type Config struct { EnableAutoRelay bool AutoNATConfig StaticRelays []peer.AddrInfo + + Introspector IntrospectorC + IntrospectionEndpoint IntrospectionEndpointC } func (cfg *Config) makeSwarm(ctx context.Context) (*swarm.Swarm, error) { @@ -185,13 +196,15 @@ func (cfg *Config) NewNode(ctx context.Context) (host.Host, error) { return nil, err } - h, err := bhost.NewHost(ctx, swrm, &bhost.HostOpts{ + opts := &bhost.HostOpts{ ConnManager: cfg.ConnManager, AddrsFactory: cfg.AddrsFactory, NATManager: cfg.NATManager, EnablePing: !cfg.DisablePing, UserAgent: cfg.UserAgent, - }) + } + + h, err := bhost.NewHost(ctx, swrm, opts) if err != nil { swrm.Close() @@ -340,6 +353,30 @@ func (cfg *Config) NewNode(ctx context.Context) (host.Host, error) { return nil, fmt.Errorf("cannot enable autorelay; autonat failed to start: %v", err) } + if cfg.Introspector != nil { + var ( + introspector introspection.Introspector + endpoint introspection.Endpoint + err error + ) + + if introspector, err = cfg.Introspector(h, cfg.Reporter); err != nil { + h.Close() + return nil, fmt.Errorf("failed to create introspector: %w", err) + } + + if cfg.IntrospectionEndpoint != nil { + if endpoint, err = cfg.IntrospectionEndpoint(introspector); err != nil { + h.Close() + return nil, fmt.Errorf("failed to create introspection endpoint: %w", err) + } + } + if err := h.SetIntrospection(introspector, endpoint); err != nil { + h.Close() + return nil, fmt.Errorf("failed to set introspection objects on host: %w", err) + } + } + // start the host background tasks h.Start() diff --git a/go.mod b/go.mod index 2fa25bb038..b78f31bcd9 100644 --- a/go.mod +++ b/go.mod @@ -3,12 +3,15 @@ module github.com/libp2p/go-libp2p go 1.12 require ( - github.com/davidlazar/go-crypto v0.0.0-20190912175916-7055855a373f // indirect + github.com/benbjohnson/clock v1.0.2 github.com/gogo/protobuf v1.3.1 - github.com/ipfs/go-cid v0.0.5 + github.com/gorilla/websocket v1.4.2 + github.com/hashicorp/go-multierror v1.1.0 + github.com/ipfs/go-cid v0.0.6 github.com/ipfs/go-detect-race v0.0.1 github.com/ipfs/go-ipfs-util v0.0.1 github.com/ipfs/go-log v1.0.4 + github.com/ipfs/go-log/v2 v2.1.1 github.com/jbenet/go-cienv v0.1.0 github.com/jbenet/goprocess v0.1.4 github.com/libp2p/go-addr-util v0.0.2 @@ -17,7 +20,7 @@ require ( github.com/libp2p/go-libp2p-autonat v0.2.3 github.com/libp2p/go-libp2p-blankhost v0.1.6 github.com/libp2p/go-libp2p-circuit v0.2.3 - github.com/libp2p/go-libp2p-core v0.5.7 + github.com/libp2p/go-libp2p-core v0.6.0 github.com/libp2p/go-libp2p-discovery v0.4.0 github.com/libp2p/go-libp2p-loggables v0.1.0 github.com/libp2p/go-libp2p-mplex v0.2.3 @@ -25,23 +28,25 @@ require ( github.com/libp2p/go-libp2p-netutil v0.1.0 github.com/libp2p/go-libp2p-peerstore v0.2.4 github.com/libp2p/go-libp2p-secio v0.2.2 - github.com/libp2p/go-libp2p-swarm v0.2.6 + github.com/libp2p/go-libp2p-swarm v0.2.7 github.com/libp2p/go-libp2p-testing v0.1.1 github.com/libp2p/go-libp2p-tls v0.1.3 github.com/libp2p/go-libp2p-transport-upgrader v0.3.0 - github.com/libp2p/go-libp2p-yamux v0.2.7 + github.com/libp2p/go-libp2p-yamux v0.2.8 github.com/libp2p/go-netroute v0.1.2 github.com/libp2p/go-sockaddr v0.1.0 // indirect github.com/libp2p/go-stream-muxer-multistream v0.3.0 github.com/libp2p/go-tcp-transport v0.2.0 github.com/libp2p/go-ws-transport v0.3.1 - github.com/libp2p/go-yamux v1.3.6 // indirect + github.com/mr-tron/base58 v1.2.0 // indirect github.com/multiformats/go-multiaddr v0.2.2 github.com/multiformats/go-multiaddr-dns v0.2.0 github.com/multiformats/go-multiaddr-net v0.1.5 github.com/multiformats/go-multistream v0.1.1 - github.com/stretchr/testify v1.5.1 + github.com/stretchr/testify v1.6.0 github.com/whyrusleeping/mdns v0.0.0-20190826153040-b9b60ed33aa9 + go.uber.org/zap v1.15.0 + golang.org/x/crypto v0.0.0-20200602180216-279210d13fed // indirect golang.org/x/net v0.0.0-20200519113804-d87ec0cfa476 // indirect - golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 // indirect + golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980 // indirect ) diff --git a/go.sum b/go.sum index 78102f9c80..623a9a31ef 100644 --- a/go.sum +++ b/go.sum @@ -14,11 +14,10 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/Kubuxu/go-os-helper v0.0.1/go.mod h1:N8B+I7vPCT80IcP58r50u4+gEEcsZETFUpAzWW2ep1Y= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= -github.com/alangpierce/go-forceexport v0.0.0-20160317203124-8f1d6941cd75 h1:3ILjVyslFbc4jl1w5TWuvvslFD/nDfR2H8tVaMVLrEY= -github.com/alangpierce/go-forceexport v0.0.0-20160317203124-8f1d6941cd75/go.mod h1:uAXEEpARkRhCZfEvy/y0Jcc888f9tHCc1W7/UeEtreE= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= -github.com/benbjohnson/clock v1.0.1/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= +github.com/benbjohnson/clock v1.0.2 h1:Z0CN0Yb4ig9sGPXkvAQcGJfnrrMQ5QYLCMPRi9iD7YE= +github.com/benbjohnson/clock v1.0.2/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= github.com/btcsuite/btcd v0.0.0-20190213025234-306aecffea32/go.mod h1:DrZx5ec/dmnfpw9KyYoQyYo7d0KEvTkk/5M/vbZjAr8= @@ -52,8 +51,6 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davidlazar/go-crypto v0.0.0-20170701192655-dcfb0a7ac018 h1:6xT9KW8zLC5IlbaIF5Q7JNieBoACT7iW0YTxQHR0in0= github.com/davidlazar/go-crypto v0.0.0-20170701192655-dcfb0a7ac018/go.mod h1:rQYf4tfk5sSwFsnDg3qYaBxSjsD9S8+59vW0dKUgme4= -github.com/davidlazar/go-crypto v0.0.0-20190912175916-7055855a373f h1:BOaYiTvg8p9vBUXpklC22XSK/mifLF7lG9jtmYYi3Tc= -github.com/davidlazar/go-crypto v0.0.0-20190912175916-7055855a373f/go.mod h1:rQYf4tfk5sSwFsnDg3qYaBxSjsD9S8+59vW0dKUgme4= github.com/dgraph-io/badger v1.5.5-0.20190226225317-8115aed38f8f/go.mod h1:VZxzAIRPHRVNRKRo6AXrX9BJegn6il06VMTZVJYCIjQ= github.com/dgraph-io/badger v1.6.0-rc1/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= @@ -86,13 +83,20 @@ github.com/golang/mock v1.4.0 h1:Rd1kQnQu0Hq3qvJppYSG0HtP+f5LPPUiDswTLiEegLg= github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0= -github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0 h1:oOuy+ugB+P/kBdUnG5QaMXSIyJ1q38wWSojYCb3z5VQ= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 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.17 h1:rMrlX2ZY2UbvT+sdz3+6J+pp2z+msCq9MxTU6ymxbBY= @@ -112,12 +116,15 @@ github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:Fecb github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= github.com/gxed/hashland/keccakpg v0.0.1/go.mod h1:kRzw3HkwxFU1mpmPP8v1WyQzwdGfmKFJ6tItnhQ67kU= github.com/gxed/hashland/murmur3 v0.0.1/go.mod h1:KjXop02n4/ckmZSnY2+HKcLud/tcmvhST0bie/0lS48= +github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI= +github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huin/goupnp v1.0.0 h1:wg75sLpL6DZqwHQN6E1Cfk6mtfzS45z8OV+ic+DtHRo= github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc= @@ -127,8 +134,9 @@ github.com/ipfs/go-cid v0.0.1/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUP github.com/ipfs/go-cid v0.0.2/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= github.com/ipfs/go-cid v0.0.3/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= github.com/ipfs/go-cid v0.0.4/go.mod h1:4LLaPOQwmk5z9LBgQnpkivrx8BJjUyGwTXCd5Xfj6+M= -github.com/ipfs/go-cid v0.0.5 h1:o0Ix8e/ql7Zb5UVUJEUfjsWCIY8t48++9lR8qi6oiJU= github.com/ipfs/go-cid v0.0.5/go.mod h1:plgt+Y5MnOey4vO4UlUazGqdbEXuFYitED67FexhXog= +github.com/ipfs/go-cid v0.0.6 h1:go0y+GcDOGeJIV01FeBsta4FHngoA4Wz7KMeLkXAhMs= +github.com/ipfs/go-cid v0.0.6/go.mod h1:6Ux9z5e+HpkQdckYoX1PG/6xqKspzlEIR5SDmgqgC/I= github.com/ipfs/go-datastore v0.0.1/go.mod h1:d4KVXhMt913cLBEI/PXAy6ko+W7e9AhyAKBGh803qeE= github.com/ipfs/go-datastore v0.4.0/go.mod h1:SX/xMIKoCszPqp+z9JhPYCmoOoXTvaa13XEbGtsFUhA= github.com/ipfs/go-datastore v0.4.1/go.mod h1:SX/xMIKoCszPqp+z9JhPYCmoOoXTvaa13XEbGtsFUhA= @@ -152,8 +160,9 @@ github.com/ipfs/go-log v1.0.4 h1:6nLQdX4W8P9yZZFH7mO+X/PzjN8Laozm/lMJ6esdgzY= github.com/ipfs/go-log v1.0.4/go.mod h1:oDCg2FkjogeFOhqqb+N39l2RpTNPL6F/StPkB3kPgcs= github.com/ipfs/go-log/v2 v2.0.2/go.mod h1:O7P1lJt27vWHhOwQmcFEvlmo49ry2VY2+JfBWFaa9+0= github.com/ipfs/go-log/v2 v2.0.3/go.mod h1:O7P1lJt27vWHhOwQmcFEvlmo49ry2VY2+JfBWFaa9+0= -github.com/ipfs/go-log/v2 v2.0.5 h1:fL4YI+1g5V/b1Yxr1qAiXTMg1H8z9vx/VmJxBuQMHvU= github.com/ipfs/go-log/v2 v2.0.5/go.mod h1:eZs4Xt4ZUJQFM3DlanGhy7TkwwawCZcSByscwkWG+dw= +github.com/ipfs/go-log/v2 v2.1.1 h1:G4TtqN+V9y9HY9TA6BwbCVyyBZ2B9MbCjR2MtGx8FR0= +github.com/ipfs/go-log/v2 v2.1.1/go.mod h1:2v2nsGfZsvvAJz13SyFzf9ObaqwHiHxsPLEHntrv9KM= github.com/jackpal/gateway v1.0.5/go.mod h1:lTpwd4ACLXmpyiCTRtfiNyVnUmqT9RivzCDQetPfnjA= github.com/jackpal/go-nat-pmp v1.0.1/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= @@ -189,7 +198,6 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/libp2p/go-addr-util v0.0.1 h1:TpTQm9cXVRVSKsYbgQ7GKc3KbbHVTnbostgGaDEP+88= github.com/libp2p/go-addr-util v0.0.1/go.mod h1:4ac6O7n9rIAKB1dnd+s8IbbMXkt+oBpzX4/+RACcnlQ= github.com/libp2p/go-addr-util v0.0.2 h1:7cWK5cdA5x72jX0g8iLrQWm5TRJZ6CzGdPEhWj7plWU= github.com/libp2p/go-addr-util v0.0.2/go.mod h1:Ecd6Fb3yIuLzq4bD7VcywcVSBtefcAwnUISBM3WG15E= @@ -199,7 +207,6 @@ github.com/libp2p/go-buffer-pool v0.0.2/go.mod h1:MvaB6xw5vOrDl8rYZGLFdKAuk/hRoR github.com/libp2p/go-conn-security-multistream v0.1.0/go.mod h1:aw6eD7LOsHEX7+2hJkDxw1MteijaVcI+/eP2/x3J1xc= github.com/libp2p/go-conn-security-multistream v0.2.0 h1:uNiDjS58vrvJTg9jO6bySd1rMKejieG7v45ekqHbZ1M= github.com/libp2p/go-conn-security-multistream v0.2.0/go.mod h1:hZN4MjlNetKD3Rq5Jb/P5ohUnFLNzEAR4DLSzpn2QLU= -github.com/libp2p/go-eventbus v0.1.0 h1:mlawomSAjjkk97QnYiEmHsLu7E136+2oCWSHRUvMfzQ= github.com/libp2p/go-eventbus v0.1.0/go.mod h1:vROgu5cs5T7cv7POWlWxBaVLxfSegC5UGQf8A2eEmx4= github.com/libp2p/go-eventbus v0.2.1 h1:VanAdErQnpTioN2TowqNcOijf6YwhuODe4pPKSDpxGc= github.com/libp2p/go-eventbus v0.2.1/go.mod h1:jc2S4SoEVPP48H9Wpzm5aiGwUCBMfGhVhhBjyhhCJs8= @@ -213,7 +220,6 @@ github.com/libp2p/go-libp2p v0.8.3/go.mod h1:EsH1A+8yoWK+L4iKcbPYu6MPluZ+CHWI9El github.com/libp2p/go-libp2p-autonat v0.1.1/go.mod h1:OXqkeGOY2xJVWKAGV2inNF5aKN/djNA3fdpCWloIudE= github.com/libp2p/go-libp2p-autonat v0.2.0/go.mod h1:DX+9teU4pEEoZUqR1PiMlqliONQdNbfzE1C718tcViI= github.com/libp2p/go-libp2p-autonat v0.2.1/go.mod h1:MWtAhV5Ko1l6QBsHQNSuM6b1sRkXrpk0/LqCr+vCVxI= -github.com/libp2p/go-libp2p-autonat v0.2.2 h1:4dlgcEEugTFWSvdG2UIFxhnOMpX76QaZSRAtXmYB8n4= github.com/libp2p/go-libp2p-autonat v0.2.2/go.mod h1:HsM62HkqZmHR2k1xgX34WuWDzk/nBwNHoeyyT4IWV6A= github.com/libp2p/go-libp2p-autonat v0.2.3 h1:w46bKK3KTOUWDe5mDYMRjJu1uryqBp8HCNDp/TWMqKw= github.com/libp2p/go-libp2p-autonat v0.2.3/go.mod h1:2U6bNWCNsAG9LEbwccBDQbjzQ8Krdjge1jLTE9rdoMM= @@ -223,11 +229,9 @@ github.com/libp2p/go-libp2p-blankhost v0.1.6 h1:CkPp1/zaCrCnBo0AdsQA0O1VkUYoUOty github.com/libp2p/go-libp2p-blankhost v0.1.6/go.mod h1:jONCAJqEP+Z8T6EQviGL4JsQcLx1LgTGtVqFNY8EMfQ= github.com/libp2p/go-libp2p-circuit v0.1.4/go.mod h1:CY67BrEjKNDhdTk8UgBX1Y/H5c3xkAcs3gnksxY7osU= github.com/libp2p/go-libp2p-circuit v0.2.1/go.mod h1:BXPwYDN5A8z4OEY9sOfr2DUQMLQvKt/6oku45YUmjIo= -github.com/libp2p/go-libp2p-circuit v0.2.2 h1:87RLabJ9lrhoiSDDZyCJ80ZlI5TLJMwfyoGAaWXzWqA= github.com/libp2p/go-libp2p-circuit v0.2.2/go.mod h1:nkG3iE01tR3FoQ2nMm06IUrCpCyJp1Eo4A1xYdpjfs4= github.com/libp2p/go-libp2p-circuit v0.2.3 h1:3Uw1fPHWrp1tgIhBz0vSOxRUmnKL8L/NGUyEd5WfSGM= github.com/libp2p/go-libp2p-circuit v0.2.3/go.mod h1:nkG3iE01tR3FoQ2nMm06IUrCpCyJp1Eo4A1xYdpjfs4= -github.com/libp2p/go-libp2p-connmgr v0.2.3/go.mod h1:Gqjg29zI8CwXX21zRxy6gOg8VYu3zVerJRt2KyktzH4= github.com/libp2p/go-libp2p-core v0.0.1/go.mod h1:g/VxnTZ/1ygHxH3dKok7Vno1VfpvGcGip57wjTU4fco= github.com/libp2p/go-libp2p-core v0.0.4/go.mod h1:jyuCQP356gzfCFtRKyvAbNkyeuxb7OlyhWZ3nls5d2I= github.com/libp2p/go-libp2p-core v0.2.0/go.mod h1:X0eyB0Gy93v0DZtSYbEM7RnMChm9Uv3j7yRXjO77xSI= @@ -241,11 +245,11 @@ github.com/libp2p/go-libp2p-core v0.5.1/go.mod h1:uN7L2D4EvPCvzSH5SrhR72UWbnSGpt github.com/libp2p/go-libp2p-core v0.5.2/go.mod h1:uN7L2D4EvPCvzSH5SrhR72UWbnSGpt5/a35Sm4upn4Y= github.com/libp2p/go-libp2p-core v0.5.3/go.mod h1:uN7L2D4EvPCvzSH5SrhR72UWbnSGpt5/a35Sm4upn4Y= github.com/libp2p/go-libp2p-core v0.5.4/go.mod h1:uN7L2D4EvPCvzSH5SrhR72UWbnSGpt5/a35Sm4upn4Y= -github.com/libp2p/go-libp2p-core v0.5.5 h1:/yiFUZDoBWqvpWeHHJ1iA8SOs5obT1/+UdNfckwD57M= github.com/libp2p/go-libp2p-core v0.5.5/go.mod h1:vj3awlOr9+GMZJFH9s4mpt9RHHgGqeHCopzbYKZdRjM= github.com/libp2p/go-libp2p-core v0.5.6/go.mod h1:txwbVEhHEXikXn9gfC7/UDDw7rkxuX0bJvM49Ykaswo= -github.com/libp2p/go-libp2p-core v0.5.7 h1:QK3xRwFxqd0Xd9bSZL+8yZ8ncZZbl6Zngd/+Y+A6sgQ= github.com/libp2p/go-libp2p-core v0.5.7/go.mod h1:txwbVEhHEXikXn9gfC7/UDDw7rkxuX0bJvM49Ykaswo= +github.com/libp2p/go-libp2p-core v0.6.0 h1:u03qofNYTBN+yVg08PuAKylZogVf0xcTEeM8skGf+ak= +github.com/libp2p/go-libp2p-core v0.6.0/go.mod h1:txwbVEhHEXikXn9gfC7/UDDw7rkxuX0bJvM49Ykaswo= github.com/libp2p/go-libp2p-crypto v0.1.0/go.mod h1:sPUokVISZiy+nNuTTH/TY+leRSxnFj/2GLjtOTW90hI= github.com/libp2p/go-libp2p-discovery v0.2.0/go.mod h1:s4VGaxYMbw4+4+tsoQTqh7wfxg97AEdo4GYBt6BadWg= github.com/libp2p/go-libp2p-discovery v0.3.0/go.mod h1:o03drFnz9BVAZdzC/QUQ+NeQOu38Fu7LJGEOK2gQltw= @@ -269,15 +273,13 @@ github.com/libp2p/go-libp2p-peerstore v0.1.3/go.mod h1:BJ9sHlm59/80oSkpWgr1MyY1c github.com/libp2p/go-libp2p-peerstore v0.2.0/go.mod h1:N2l3eVIeAitSg3Pi2ipSrJYnqhVnMNQZo9nkSCuAbnQ= github.com/libp2p/go-libp2p-peerstore v0.2.1/go.mod h1:NQxhNjWxf1d4w6PihR8btWIRjwRLBr4TYKfNgrUkOPA= github.com/libp2p/go-libp2p-peerstore v0.2.2/go.mod h1:NQxhNjWxf1d4w6PihR8btWIRjwRLBr4TYKfNgrUkOPA= -github.com/libp2p/go-libp2p-peerstore v0.2.3 h1:MofRq2l3c15vQpEygTetV+zRRrncz+ktiXW7H2EKoEQ= github.com/libp2p/go-libp2p-peerstore v0.2.3/go.mod h1:K8ljLdFn590GMttg/luh4caB/3g0vKuY01psze0upRw= github.com/libp2p/go-libp2p-peerstore v0.2.4 h1:jU9S4jYN30kdzTpDAR7SlHUD+meDUjTODh4waLWF1ws= github.com/libp2p/go-libp2p-peerstore v0.2.4/go.mod h1:ss/TWTgHZTMpsU/oKVVPQCGuDHItOpf2W8RxAi50P2s= github.com/libp2p/go-libp2p-pnet v0.2.0 h1:J6htxttBipJujEjz1y0a5+eYoiPcFHhSYHH6na5f0/k= github.com/libp2p/go-libp2p-pnet v0.2.0/go.mod h1:Qqvq6JH/oMZGwqs3N1Fqhv8NVhrdYcO0BW4wssv21LA= -github.com/libp2p/go-libp2p-pubsub v0.3.1/go.mod h1:TxPOBuo1FPdsTjFnv+FGZbNbWYsp74Culx+4ViQpato= -github.com/libp2p/go-libp2p-quic-transport v0.3.7 h1:F9hxonkJvMipNim8swrvRk2uL9s8pqzHz0M6eMf8L58= -github.com/libp2p/go-libp2p-quic-transport v0.3.7/go.mod h1:Kr4aDtnfHHNeENn5J+sZIVc+t8HpQn9W6BOxhVGHbgI= +github.com/libp2p/go-libp2p-quic-transport v0.5.0 h1:BUN1lgYNUrtv4WLLQ5rQmC9MCJ6uEXusezGvYRNoJXE= +github.com/libp2p/go-libp2p-quic-transport v0.5.0/go.mod h1:IEcuC5MLxvZ5KuHKjRu+dr3LjCT1Be3rcD/4d8JrX8M= github.com/libp2p/go-libp2p-secio v0.1.0/go.mod h1:tMJo2w7h3+wN4pgU2LSYeiKPrfqBgkOsdiKK77hE7c8= github.com/libp2p/go-libp2p-secio v0.2.0/go.mod h1:2JdZepB8J5V9mBp79BmwsaPQhRPNN2NrnB2lKQcdy6g= github.com/libp2p/go-libp2p-secio v0.2.1/go.mod h1:cWtZpILJqkqrSkiYcDBh5lA3wbT2Q+hz3rJQq3iftD8= @@ -286,10 +288,8 @@ github.com/libp2p/go-libp2p-secio v0.2.2/go.mod h1:wP3bS+m5AUnFA+OFO7Er03uO1mncH github.com/libp2p/go-libp2p-swarm v0.1.0/go.mod h1:wQVsCdjsuZoc730CgOvh5ox6K8evllckjebkdiY5ta4= github.com/libp2p/go-libp2p-swarm v0.2.2/go.mod h1:fvmtQ0T1nErXym1/aa1uJEyN7JzaTNyBcHImCxRpPKU= github.com/libp2p/go-libp2p-swarm v0.2.3/go.mod h1:P2VO/EpxRyDxtChXz/VPVXyTnszHvokHKRhfkEgFKNM= -github.com/libp2p/go-libp2p-swarm v0.2.4 h1:94XL76/tFeTdJNcIGugi+1uZo5O/a7y4i21PirwbgZI= -github.com/libp2p/go-libp2p-swarm v0.2.4/go.mod h1:/xIpHFPPh3wmSthtxdGbkHZ0OET1h/GGZes8Wku/M5Y= -github.com/libp2p/go-libp2p-swarm v0.2.6 h1:UhMXIa+yCOALQyceENEIStMlbTCzOM6aWo6vw8QW17Q= -github.com/libp2p/go-libp2p-swarm v0.2.6/go.mod h1:F9hrkZjO7dDbcEiYii/fAB1QdpLuU6h1pa4P5VNsEgc= +github.com/libp2p/go-libp2p-swarm v0.2.7 h1:4lV/sf7f0NuVqunOpt1I11+Z54+xp+m0eeAvxj/LyRc= +github.com/libp2p/go-libp2p-swarm v0.2.7/go.mod h1:ZSJ0Q+oq/B1JgfPHJAT2HTall+xYRNYp1xs4S2FBWKA= github.com/libp2p/go-libp2p-testing v0.0.2/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E= github.com/libp2p/go-libp2p-testing v0.0.3/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E= github.com/libp2p/go-libp2p-testing v0.0.4/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E= @@ -305,10 +305,10 @@ github.com/libp2p/go-libp2p-transport-upgrader v0.3.0/go.mod h1:i+SKzbRnvXdVbU3D github.com/libp2p/go-libp2p-yamux v0.2.0/go.mod h1:Db2gU+XfLpm6E4rG5uGCFX6uXA8MEXOxFcRoXUODaK8= github.com/libp2p/go-libp2p-yamux v0.2.2/go.mod h1:lIohaR0pT6mOt0AZ0L2dFze9hds9Req3OfS+B+dv4qw= github.com/libp2p/go-libp2p-yamux v0.2.5/go.mod h1:Zpgj6arbyQrmZ3wxSZxfBmbdnWtbZ48OpsfmQVTErwA= -github.com/libp2p/go-libp2p-yamux v0.2.7 h1:vzKu0NVtxvEIDGCv6mjKRcK0gipSgaXmJZ6jFv0d/dk= github.com/libp2p/go-libp2p-yamux v0.2.7/go.mod h1:X28ENrBMU/nm4I3Nx4sZ4dgjZ6VhLEn0XhIoZ5viCwU= +github.com/libp2p/go-libp2p-yamux v0.2.8 h1:0s3ELSLu2O7hWKfX1YjzudBKCP0kZ+m9e2+0veXzkn4= +github.com/libp2p/go-libp2p-yamux v0.2.8/go.mod h1:/t6tDqeuZf0INZMTgd0WxIRbtK2EzI2h7HbFm9eAKI4= github.com/libp2p/go-maddr-filter v0.0.4/go.mod h1:6eT12kSQMA9x2pvFQa+xesMKUBlj9VImZbj3B9FBH/Q= -github.com/libp2p/go-maddr-filter v0.0.5 h1:CW3AgbMO6vUvT4kf87y4N+0P8KUl2aqLYhrGyDUbLSg= github.com/libp2p/go-maddr-filter v0.0.5/go.mod h1:Jk+36PMfIqCJhAnaASRH83bdAvfDRp/w6ENFaC9bG+M= github.com/libp2p/go-mplex v0.0.3/go.mod h1:pK5yMLmOoBR1pNCqDlA2GQrdAVTMkqFalaTWe7l4Yd0= github.com/libp2p/go-mplex v0.1.0/go.mod h1:SXgmdki2kwCUlCCbfGLEgHjC4pFqhTp0ZoV6aiKgxDU= @@ -325,7 +325,6 @@ github.com/libp2p/go-netroute v0.1.2 h1:UHhB35chwgvcRI392znJA3RCBtZ3MpE3ahNCN5MR github.com/libp2p/go-netroute v0.1.2/go.mod h1:jZLDV+1PE8y5XxBySEBgbuVAXbhtuHSdmLPL2n9MKbk= github.com/libp2p/go-openssl v0.0.2/go.mod h1:v8Zw2ijCSWBQi8Pq5GAixw6DbFfa9u6VIYDXnvOXkc0= github.com/libp2p/go-openssl v0.0.3/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= -github.com/libp2p/go-openssl v0.0.4 h1:d27YZvLoTyMhIN4njrkr8zMDOM4lfpHIp6A+TK9fovg= github.com/libp2p/go-openssl v0.0.4/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= github.com/libp2p/go-openssl v0.0.5 h1:pQkejVhF0xp08D4CQUcw8t+BFJeXowja6RVcb5p++EA= github.com/libp2p/go-openssl v0.0.5/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= @@ -334,7 +333,6 @@ github.com/libp2p/go-reuseport v0.0.1/go.mod h1:jn6RmB1ufnQwl0Q1f+YxAj8isJgDCQza github.com/libp2p/go-reuseport-transport v0.0.2/go.mod h1:YkbSDrvjUVDL6b8XqriyA20obEtsW9BLkuOUyQAOCbs= github.com/libp2p/go-reuseport-transport v0.0.3 h1:zzOeXnTooCkRvoH+bSXEfXhn76+LAiwoneM0gnXjF2M= github.com/libp2p/go-reuseport-transport v0.0.3/go.mod h1:Spv+MPft1exxARzP2Sruj2Wb5JSyHNncjf1Oi2dEbzM= -github.com/libp2p/go-sockaddr v0.0.2 h1:tCuXfpA9rq7llM/v834RKc/Xvovy/AqM9kHvTV/jY/Q= github.com/libp2p/go-sockaddr v0.0.2/go.mod h1:syPvOmNs24S3dFVGJA1/mrqdeijPxLV2Le3BRLKd68k= github.com/libp2p/go-sockaddr v0.1.0 h1:Y4s3/jNoryVRKEBrkJ576F17CPOaMIzUeCsg7dlTDj0= github.com/libp2p/go-sockaddr v0.1.0/go.mod h1:syPvOmNs24S3dFVGJA1/mrqdeijPxLV2Le3BRLKd68k= @@ -353,12 +351,11 @@ github.com/libp2p/go-ws-transport v0.3.1/go.mod h1:bpgTJmRZAvVHrgHybCVyqoBmyLQ1f github.com/libp2p/go-yamux v1.2.2/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= github.com/libp2p/go-yamux v1.3.0/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= github.com/libp2p/go-yamux v1.3.3/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= -github.com/libp2p/go-yamux v1.3.5 h1:ibuz4naPAully0pN6J/kmUARiqLpnDQIzI/8GCOrljg= github.com/libp2p/go-yamux v1.3.5/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= -github.com/libp2p/go-yamux v1.3.6 h1:O5qcBXRcfqecvQ/My9NqDNHB3/5t58yuJYqthcKhhgE= -github.com/libp2p/go-yamux v1.3.6/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= -github.com/lucas-clemente/quic-go v0.15.7 h1:Pu7To5/G9JoP1mwlrcIvfV8ByPBlCzif3MCl8+1W83I= -github.com/lucas-clemente/quic-go v0.15.7/go.mod h1:Myi1OyS0FOjL3not4BxT7KN29bRkcMUV5JVVFLKtDp8= +github.com/libp2p/go-yamux v1.3.7 h1:v40A1eSPJDIZwz2AvrV3cxpTZEGDP11QJbukmEhYyQI= +github.com/libp2p/go-yamux v1.3.7/go.mod h1:fr7aVgmdNGJK+N1g+b6DW6VxzbRCjCOejR/hkmpooHE= +github.com/lucas-clemente/quic-go v0.16.0 h1:jJw36wfzGJhmOhAOaOC2lS36WgeqXQszH47A7spo1LI= +github.com/lucas-clemente/quic-go v0.16.0/go.mod h1:I0+fcNTdb9eS1ZcjQZbDVPGchJ86chcIxPALn9lEJqE= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -391,17 +388,19 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb github.com/mr-tron/base58 v1.1.0/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8= github.com/mr-tron/base58 v1.1.1/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8= github.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= -github.com/mr-tron/base58 v1.1.3 h1:v+sk57XuaCKGXpWtVBX8YJzO7hMGx4Aajh4TQbdEFdc= github.com/mr-tron/base58 v1.1.3/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= +github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= +github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/multiformats/go-base32 v0.0.3 h1:tw5+NhuwaOjJCC5Pp82QuXbrmLzWg7uxlMFp8Nq/kkI= github.com/multiformats/go-base32 v0.0.3/go.mod h1:pLiuGC8y0QR3Ue4Zug5UzK9LjgbkL8NSQj0zQ5Nz/AA= +github.com/multiformats/go-base36 v0.1.0 h1:JR6TyF7JjGd3m6FbLU2cOxhC0Li8z8dLNGQ89tUg4F4= +github.com/multiformats/go-base36 v0.1.0/go.mod h1:kFGE83c6s80PklsHO9sRn2NCoffoRdUUOENyW/Vv6sM= github.com/multiformats/go-multiaddr v0.0.1/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44= github.com/multiformats/go-multiaddr v0.0.2/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44= github.com/multiformats/go-multiaddr v0.0.4/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44= github.com/multiformats/go-multiaddr v0.1.0/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44= github.com/multiformats/go-multiaddr v0.1.1/go.mod h1:aMKBKNEYmzmDmxfX88/vz+J5IU55txyt0p4aiWVohjo= github.com/multiformats/go-multiaddr v0.2.0/go.mod h1:0nO36NvPpyV4QzvTLi/lafl2y95ncPj0vFwVF6k6wJ4= -github.com/multiformats/go-multiaddr v0.2.1 h1:SgG/cw5vqyB5QQe5FPe2TqggU9WtrA9X4nZw7LlVqOI= github.com/multiformats/go-multiaddr v0.2.1/go.mod h1:s/Apk6IyxfvMjDafnhJgJ3/46z7tZ04iMk5wP4QMGGE= github.com/multiformats/go-multiaddr v0.2.2 h1:XZLDTszBIJe6m0zF6ITBrEcZR73OPUhCBBS9rYAuUzI= github.com/multiformats/go-multiaddr v0.2.2/go.mod h1:NtfXiOtHvghW9KojvtySjH5y0u0xW5UouOmQQrn6a3Y= @@ -420,10 +419,9 @@ github.com/multiformats/go-multiaddr-net v0.1.3/go.mod h1:ilNnaM9HbmVFqsb/qcNysj github.com/multiformats/go-multiaddr-net v0.1.4/go.mod h1:ilNnaM9HbmVFqsb/qcNysjCu4PVONlrBZpHIrw/qQuA= github.com/multiformats/go-multiaddr-net v0.1.5 h1:QoRKvu0xHN1FCFJcMQLbG/yQE2z441L5urvG3+qyz7g= github.com/multiformats/go-multiaddr-net v0.1.5/go.mod h1:ilNnaM9HbmVFqsb/qcNysjCu4PVONlrBZpHIrw/qQuA= -github.com/multiformats/go-multibase v0.0.1 h1:PN9/v21eLywrFWdFNsFKaU04kLJzuYzmrJR+ubhT9qA= github.com/multiformats/go-multibase v0.0.1/go.mod h1:bja2MqRZ3ggyXtZSEDKpl0uO/gviWFaSteVbWT51qgs= -github.com/multiformats/go-multibase v0.0.2 h1:2pAgScmS1g9XjH7EtAfNhTuyrWYEWcxy0G5Wo85hWDA= -github.com/multiformats/go-multibase v0.0.2/go.mod h1:bja2MqRZ3ggyXtZSEDKpl0uO/gviWFaSteVbWT51qgs= +github.com/multiformats/go-multibase v0.0.3 h1:l/B6bJDQjvQ5G52jw4QGSYeOTZoAwIO77RblWplfIqk= +github.com/multiformats/go-multibase v0.0.3/go.mod h1:5+1R4eQrT3PkYZ24C3W2Ue2tPwIdYQD509ZjSb5y9Oc= github.com/multiformats/go-multihash v0.0.1/go.mod h1:w/5tugSrLEbWqlcgJabL3oHFKTwfvkofsjW2Qa1ct4U= github.com/multiformats/go-multihash v0.0.5/go.mod h1:lt/HCbqlQwlPBz7lv0sQCdtfcMtlJvakRUn/0Ual8po= github.com/multiformats/go-multihash v0.0.8/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew= @@ -439,12 +437,15 @@ github.com/multiformats/go-varint v0.0.5 h1:XVZwSo04Cs3j/jS0uAEPpT3JY6DzMcVLLoWO github.com/multiformats/go-varint v0.0.5/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= 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/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.0 h1:Iw5WCbBcaAAd0fpRb1c9r5YCylv4XDoCSigm1zLevwU= github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= +github.com/onsi/ginkgo v1.12.1 h1:mFwc4LvZ0xpSvDZ3E+k8Yte0hLOMxXUlP+yXtJqkYfQ= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= @@ -508,12 +509,14 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/src-d/envconfig v1.0.0/go.mod h1:Q9YQZ7BKITldTBnoxsE5gOeB5y66RyPXeue/R4aaNBc= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 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.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.0 h1:jlIyCplCJFULU/01vCkhKuTyc3OorI3bJFuw6obfgho= +github.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= @@ -522,13 +525,11 @@ github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMI github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1/go.mod h1:8UvriyWtv5Q5EOgjHaSseUEdkQfvwFv1I/In/O2M9gc= github.com/whyrusleeping/go-logging v0.0.0-20170515211332-0457bb6b88fc/go.mod h1:bopw91TMyo8J3tvftk8xmU2kPmlrt4nScJQZU2hE5EM= github.com/whyrusleeping/go-logging v0.0.1/go.mod h1:lDPYj54zutzG1XYfHAhcc7oNXEburHQBn+Iqd4yS4vE= -github.com/whyrusleeping/mafmt v1.2.8 h1:TCghSl5kkwEE0j+sU/gudyhVMRlpBin8fMBBHg59EbA= github.com/whyrusleeping/mafmt v1.2.8/go.mod h1:faQJFPbLSxzD9xpA02ttW/tS9vZykNvXwGvqIpk20FA= github.com/whyrusleeping/mdns v0.0.0-20190826153040-b9b60ed33aa9 h1:Y1/FEOpaCpD21WxrmfeIYCFPuVPRCY2XZTWzTNHGw30= github.com/whyrusleeping/mdns v0.0.0-20190826153040-b9b60ed33aa9/go.mod h1:j4l84WPFclQPj320J9gp0XwNKBb3U0zt5CBqjPp22G4= github.com/whyrusleeping/multiaddr-filter v0.0.0-20160516205228-e903e4adabd7 h1:E9S12nwJwEOXe2d6gT6qxdvqMnNq+VnSsKPgm2ZZNds= github.com/whyrusleeping/multiaddr-filter v0.0.0-20160516205228-e903e4adabd7/go.mod h1:X2c0RVCI1eSUFI8eLcY3c0423ykwiUdxLJtkDvruhjI= -github.com/whyrusleeping/timecache v0.0.0-20160911033111-cfcb2f1abfee/go.mod h1:m2aV4LZI4Aez7dP5PMyVKEHhUyEJ/RjmPEDOpDvudHg= github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= @@ -548,7 +549,6 @@ go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKY go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.14.1 h1:nYDKopTbvAPq/NrUVZwT15y2lpROBiLLyoRTbXOYWOo= go.uber.org/zap v1.14.1/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM= go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= @@ -570,8 +570,8 @@ golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 h1:cg5LA/zNPRzIXIWSCxQW10Rvpy94aQh3LT/ShoCpkHw= -golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200602180216-279210d13fed h1:g4KENRiCMEx58Q7/ecwfT0N2o8z35Fnbsjig/Alf2T4= +golang.org/x/crypto v0.0.0-20200602180216-279210d13fed/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 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= @@ -595,7 +595,6 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190923162816-aa69164e4478 h1:l5EDrHhldLYb3ZRHDUhXF7Om7MvYXnkV9/iQNo1lX6g= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200519113804-d87ec0cfa476 h1:E7ct1C6/33eOdrGZKMoyntcEvs2dwZnDe30crG5vpYU= golang.org/x/net v0.0.0-20200519113804-d87ec0cfa476/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= @@ -625,14 +624,14 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190526052359-791d8a0f4d09/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200509044756-6aff5f38e54f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 h1:DYfZAGf2WMFjMxbgTjaC+2HC7NkNAQs+6Q8b9WEB/F4= -golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980 h1:OjiUf46hAmXblsZdnoSXsEUSKU8r1UEzcL5RVZ4gO9Y= +golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -657,8 +656,9 @@ golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191216052735-49a3e744a425 h1:VvQyQJN0tSuecqgcIxMWnnfG5kSmgy9KZR9sW3W5QeA= golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 h1:/atklqdjdhuosWIl6AIbOeHJjicWYPqR9bpxqxYG2pA= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y= @@ -678,12 +678,18 @@ google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9M google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 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-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/src-d/go-cli.v0 v0.0.0-20181105080154-d492247bbc0d/go.mod h1:z+K8VcOYVYcSwSjGebuDL6176A1XskgbtNl64NSg+n8= @@ -694,6 +700,8 @@ 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.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/introspect/default_inspector.go b/introspect/default_inspector.go new file mode 100644 index 0000000000..6aedd474b7 --- /dev/null +++ b/introspect/default_inspector.go @@ -0,0 +1,243 @@ +package introspect + +import ( + "fmt" + "math" + "runtime" + "time" + + "github.com/libp2p/go-libp2p-core/event" + "github.com/libp2p/go-libp2p-core/host" + "github.com/libp2p/go-libp2p-core/introspection" + "github.com/libp2p/go-libp2p-core/introspection/pb" + "github.com/libp2p/go-libp2p-core/metrics" + "github.com/libp2p/go-libp2p-core/network" + "github.com/libp2p/go-libp2p-core/peer" + + "github.com/libp2p/go-eventbus" + "github.com/multiformats/go-multiaddr" + + "github.com/hashicorp/go-multierror" +) + +var _ introspection.Introspector = (*DefaultIntrospector)(nil) + +// DefaultIntrospector is an object that introspects the system. +type DefaultIntrospector struct { + *eventManager + + host host.Host + bus event.Bus + wsub event.Subscription + reporter metrics.Reporter + started time.Time +} + +func NewDefaultIntrospector(host host.Host, reporter metrics.Reporter) (introspection.Introspector, error) { + bus := host.EventBus() + if bus == nil { + return nil, fmt.Errorf("introspector requires a host with eventbus capability") + } + + sub, err := bus.Subscribe(event.WildcardSubscription, eventbus.BufSize(256)) + if err != nil { + return nil, fmt.Errorf("failed to susbcribe for events with WildcardSubscription") + } + + d := &DefaultIntrospector{ + eventManager: newEventManager(sub.Out()), + host: host, + bus: bus, + wsub: sub, + reporter: reporter, + started: time.Now(), + } + + return d, nil +} + +func (d *DefaultIntrospector) Close() error { + var err *multierror.Error + if err := d.wsub.Close(); err != nil { + err = multierror.Append(err, fmt.Errorf("failed while trying to close wildcard eventbus subscription: %w", err)) + } + + close(d.closeCh) + d.closeWg.Wait() + + return err.ErrorOrNil() +} + +func (d *DefaultIntrospector) FetchRuntime() (*pb.Runtime, error) { + return &pb.Runtime{ + Implementation: "go-libp2p", + Platform: runtime.GOOS, + PeerId: d.host.ID().Pretty(), + Version: "", + }, nil +} + +func (d *DefaultIntrospector) FetchFullState() (state *pb.State, err error) { + var ( + now = time.Now() + netconns = d.host.Network().Conns() + conns = make([]*pb.Connection, 0, len(netconns)) + traffic *pb.Traffic + ) + + for _, conn := range netconns { + c, err := d.IntrospectConnection(conn) + if err != nil { + return nil, err + } + conns = append(conns, c) + } + + // subsystems and traffic. + traffic, err = d.IntrospectGlobalTraffic() + if err != nil { + return nil, err + } + + state = &pb.State{ + // timestamps in millis since epoch. + StartTs: uint64(d.started.UnixNano() / int64(time.Millisecond)), + InstantTs: uint64(now.UnixNano() / int64(time.Millisecond)), + Subsystems: &pb.Subsystems{ + Connections: conns, + }, + Traffic: traffic, + } + + return state, nil +} + +// IntrospectGlobalTraffic introspects and returns total traffic stats for this swarm. +func (d *DefaultIntrospector) IntrospectGlobalTraffic() (*pb.Traffic, error) { + if d.reporter == nil { + return nil, nil + } + + metrics := d.reporter.GetBandwidthTotals() + t := &pb.Traffic{ + TrafficIn: &pb.DataGauge{ + CumBytes: uint64(metrics.TotalIn), + InstBw: uint64(metrics.RateIn), + }, + TrafficOut: &pb.DataGauge{ + CumBytes: uint64(metrics.TotalOut), + InstBw: uint64(metrics.RateOut), + }, + } + + return t, nil +} + +func (d *DefaultIntrospector) IntrospectConnection(conn network.Conn) (*pb.Connection, error) { + stat := conn.Stat() + openTs := uint64(stat.Opened.UnixNano() / 1000000) + + res := &pb.Connection{ + Id: []byte(conn.ID()), + Status: pb.Status_ACTIVE, + PeerId: conn.RemotePeer().Pretty(), + Endpoints: &pb.EndpointPair{ + SrcMultiaddr: conn.LocalMultiaddr().String(), + DstMultiaddr: conn.RemoteMultiaddr().String(), + }, + Role: translateRole(stat), + + Timeline: &pb.Connection_Timeline{ + OpenTs: openTs, + UpgradedTs: openTs, + // TODO ClosedTs, UpgradedTs. + }, + } + + // TODO this is a per-peer, not a per-conn measurement. In the future, when + // we have multiple connections per peer, this will produce inaccurate + // numbers. Also, we do not record stream-level stats. + // We don't have packet I/O stats. + if r := d.reporter; r != nil { + bw := r.GetBandwidthForPeer(conn.RemotePeer()) + res.Traffic = &pb.Traffic{ + TrafficIn: &pb.DataGauge{ + CumBytes: uint64(bw.TotalIn), + InstBw: uint64(math.Round(bw.RateIn)), + }, + TrafficOut: &pb.DataGauge{ + CumBytes: uint64(bw.TotalOut), + InstBw: uint64(math.Round(bw.RateOut)), + }, + } + } + + // TODO I don't think we pin the multiplexer and the secure channel we've + // negotiated anywhere. + res.Attribs = &pb.Connection_Attributes{} + + // TransportId with format "ip4+tcp" or "ip6+udp+quic". + res.TransportId = func() []byte { + tptAddr, _ := peer.SplitAddr(conn.RemoteMultiaddr()) + var str string + multiaddr.ForEach(tptAddr, func(c multiaddr.Component) bool { + str += c.Protocol().Name + "+" + return true + }) + return []byte(str[0 : len(str)-1]) + }() + + // TODO there's the ping protocol, but that's higher than this layer. + // How do we source this? We may need some kind of latency manager. + res.LatencyNs = 0 + + streams := conn.GetStreams() + res.Streams = &pb.StreamList{ + Streams: make([]*pb.Stream, 0, len(streams)), + } + + for _, stream := range streams { + s, err := d.IntrospectStream(stream) + if err != nil { + return nil, err + } + res.Streams.Streams = append(res.Streams.Streams, s) + } + + return res, nil +} + +func (d *DefaultIntrospector) IntrospectStream(stream network.Stream) (*pb.Stream, error) { + stat := stream.Stat() + openTs := uint64(stat.Opened.UnixNano() / 1000000) + + res := &pb.Stream{ + Id: []byte(stream.ID()), + Status: pb.Status_ACTIVE, + Conn: &pb.Stream_ConnectionRef{ + Connection: &pb.Stream_ConnectionRef_ConnId{ + ConnId: []byte(stream.Conn().ID()), + }, + }, + Protocol: string(stream.Protocol()), + Role: translateRole(stat), + Timeline: &pb.Stream_Timeline{ + OpenTs: openTs, + // TODO CloseTs. + }, + // TODO Traffic: we are not tracking per-stream traffic stats at the moment. + Traffic: &pb.Traffic{TrafficIn: &pb.DataGauge{}, TrafficOut: &pb.DataGauge{}}, + } + return res, nil +} + +func translateRole(stat network.Stat) pb.Role { + switch stat.Direction { + case network.DirInbound: + return pb.Role_RESPONDER + case network.DirOutbound: + return pb.Role_INITIATOR + default: + return 99 // TODO placeholder value + } +} diff --git a/introspect/default_inspector_test.go b/introspect/default_inspector_test.go new file mode 100644 index 0000000000..5260737771 --- /dev/null +++ b/introspect/default_inspector_test.go @@ -0,0 +1,113 @@ +package introspect_test + +import ( + "context" + "testing" + "time" + + "github.com/libp2p/go-libp2p-core/introspection/pb" + "github.com/libp2p/go-libp2p-core/metrics" + "github.com/libp2p/go-libp2p-core/network" + "github.com/libp2p/go-libp2p-core/peerstore" + "github.com/libp2p/go-libp2p-core/protocol" + + "github.com/libp2p/go-libp2p" + "github.com/libp2p/go-libp2p/introspect" + + "github.com/stretchr/testify/require" +) + +func TestConnsAndStreamIntrospect(t *testing.T) { + ctx := context.Background() + + bwc1 := metrics.NewBandwidthCounter() + h1, err := libp2p.New(ctx, libp2p.BandwidthReporter(bwc1)) + require.NoError(t, err) + + bwc2 := metrics.NewBandwidthCounter() + h2, err := libp2p.New(ctx, libp2p.BandwidthReporter(bwc2)) + require.NoError(t, err) + + introspector1, err := introspect.NewDefaultIntrospector(h1, bwc1) + require.NoError(t, err) + _, _ = introspect.NewDefaultIntrospector(h2, bwc2) + + h1.Peerstore().AddAddrs(h2.ID(), h2.Network().ListenAddresses(), peerstore.PermanentAddrTTL) + err = h1.Connect(ctx, h2.Peerstore().PeerInfo(h2.ID())) + require.NoError(t, err) + + // ----- H1 opens two streams to H2 + pid1, pid2 := protocol.ID("1"), protocol.ID("2") + h2.SetStreamHandler(pid1, func(stream network.Stream) {}) + h2.SetStreamHandler(pid2, func(stream network.Stream) {}) + + s1, err := h1.NewStream(ctx, h2.ID(), pid1) + require.NoError(t, err) + s2, err := h1.NewStream(ctx, h2.ID(), pid2) + require.NoError(t, err) + + // send 4 bytes on stream 1 & 5 bytes on stream 2 + msg1 := "abcd" + msg2 := "12345" + _, err = s1.Write([]byte(msg1)) + require.NoError(t, err) + _, err = s2.Write([]byte(msg2)) + require.NoError(t, err) + + // wait for the metrics to kick in + require.Eventually(t, func() bool { + state, _ := introspector1.FetchFullState() + return state.Traffic.TrafficOut.CumBytes != 0 + }, 3*time.Second, 100*time.Millisecond) + + // ----- Introspect host 1. + state, err := introspector1.FetchFullState() + require.NoError(t, err) + conns := state.Subsystems.Connections + + // connection asserts + require.Len(t, conns, 1) + require.NotEmpty(t, conns[0].Id) + require.Equal(t, h2.ID().String(), conns[0].PeerId) + require.Equal(t, pb.Status_ACTIVE, conns[0].Status) + require.Equal(t, pb.Role_INITIATOR, conns[0].Role) + require.Equal(t, h1.Network().Conns()[0].LocalMultiaddr().String(), conns[0].Endpoints.SrcMultiaddr) + require.Equal(t, h1.Network().Conns()[0].RemoteMultiaddr().String(), conns[0].Endpoints.DstMultiaddr) + + // stream asserts. + streams := conns[0].Streams.Streams + require.Len(t, streams, 2) + require.NoError(t, err) + + // map stream to protocols + protos := make(map[string]*pb.Stream) + for _, s := range streams { + protos[s.Protocol] = s + } + + // introspect stream 1 + stream1 := protos["1"] + require.NotEmpty(t, stream1) + require.Equal(t, "1", stream1.Protocol) + require.Equal(t, pb.Role_INITIATOR, stream1.Role) + require.Equal(t, pb.Status_ACTIVE, stream1.Status) + require.NotEmpty(t, stream1.Id) + require.NotNil(t, stream1.Traffic) + require.NotNil(t, stream1.Traffic.TrafficIn) + require.NotNil(t, stream1.Traffic.TrafficOut) + + // introspect stream 2 + stream2 := protos["2"] + require.NotEmpty(t, stream2) + require.Equal(t, "2", stream2.Protocol) + require.Equal(t, pb.Role_INITIATOR, stream2.Role) + require.Equal(t, pb.Status_ACTIVE, stream2.Status) + require.NotEmpty(t, stream2.Id) + require.NotEqual(t, stream2.Id, stream1.Id) + + // introspect traffic + tr := state.Traffic + require.NoError(t, err) + require.NotZero(t, tr.TrafficOut.CumBytes) + require.Zero(t, tr.TrafficIn.CumBytes == 0) +} diff --git a/introspect/event_manager.go b/introspect/event_manager.go new file mode 100644 index 0000000000..03aadcf3c6 --- /dev/null +++ b/introspect/event_manager.go @@ -0,0 +1,194 @@ +package introspect + +import ( + "encoding/json" + "errors" + "fmt" + "reflect" + "sync" + "time" + + "github.com/ipfs/go-log/v2" + + "github.com/libp2p/go-libp2p-core/event" + "github.com/libp2p/go-libp2p-core/introspection/pb" + "github.com/libp2p/go-libp2p-core/peer" + + "github.com/multiformats/go-multiaddr" +) + +var ( + jsType = reflect.TypeOf(new(event.RawJSON)).Elem() + peerIdType = reflect.TypeOf(new(peer.ID)).Elem() + timeType = reflect.TypeOf(new(time.Time)).Elem() + maddrType = reflect.TypeOf(new(multiaddr.Multiaddr)).Elem() +) + +type eventManager struct { + sync.RWMutex + logger *log.ZapEventLogger + + inCh <-chan interface{} + outCh chan *pb.Event + metadata map[reflect.Type]*pb.EventType + + closed bool + closeCh chan struct{} + closeWg sync.WaitGroup +} + +func newEventManager(inCh <-chan interface{}) *eventManager { + em := &eventManager{ + inCh: inCh, + logger: log.Logger("introspection/event-manager"), + outCh: make(chan *pb.Event, cap(inCh)), + closeCh: make(chan struct{}), + metadata: make(map[reflect.Type]*pb.EventType), + } + + em.closeWg.Add(1) + go em.processEvents() + return em +} + +func (em *eventManager) Close() error { + em.Lock() + defer em.Unlock() + + if em.closed { + em.closeWg.Wait() + return nil + } + + close(em.closeCh) + em.closeWg.Wait() + return nil +} + +func (em *eventManager) EventChan() <-chan *pb.Event { + return em.outCh +} + +func (em *eventManager) EventMetadata() []*pb.EventType { + em.RLock() + defer em.RUnlock() + + res := make([]*pb.EventType, 0, len(em.metadata)) + for k := range em.metadata { + v := em.metadata[k] + res = append(res, v) + } + return res +} + +func (em *eventManager) processEvents() { + defer em.closeWg.Done() + defer close(em.outCh) + + for { + select { + case <-em.closeCh: + return + + case evt, more := <-em.inCh: + if !more { + return + } + + e, err := em.createEvent(evt) + if err != nil { + em.logger.Warnf("failed to process event; err: %s", err) + continue + } + + select { + case em.outCh <- e: + case <-em.closeCh: + return + default: + em.logger.Warnf("failed to queue event") + } + } + } +} + +func (em *eventManager) createEvent(evt interface{}) (*pb.Event, error) { + js, err := json.Marshal(evt) + if err != nil { + return nil, fmt.Errorf("failed to marshal event to json; err: %w", err) + } + + ret := &pb.Event{ + Type: &pb.EventType{}, + Ts: uint64(time.Now().UnixNano() / int64(time.Millisecond)), + Content: string(js), + } + + key := reflect.TypeOf(evt) + + em.RLock() + et, ok := em.metadata[key] + em.RUnlock() + + if ok { + // just send the name if we've already seen the event before + ret.Type.Name = et.Name + return ret, nil + } + + if key.Kind() != reflect.Struct { + return nil, errors.New("event type must be a struct") + } + + ret.Type.Name = key.Name() + ret.Type.PropertyTypes = make([]*pb.EventType_EventProperty, 0, key.NumField()) + + for i := 0; i < key.NumField(); i++ { + fld := key.Field(i) + fldType := fld.Type + + prop := &pb.EventType_EventProperty{} + prop.Name = fld.Name + + if fldType.Kind() == reflect.Array || fldType.Kind() == reflect.Slice { + prop.HasMultiple = true + fldType = fld.Type.Elem() + } + + switch fldType { + case jsType: + prop.Type = pb.EventType_EventProperty_JSON + case peerIdType: + prop.Type = pb.EventType_EventProperty_PEERID + case maddrType: + prop.Type = pb.EventType_EventProperty_MULTIADDR + case timeType: + prop.Type = pb.EventType_EventProperty_TIME + default: + switch fldType.Kind() { + case reflect.String: + prop.Type = pb.EventType_EventProperty_STRING + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, + reflect.Uint64, reflect.Float32, reflect.Float64: + prop.Type = pb.EventType_EventProperty_NUMBER + default: + prop.Type = pb.EventType_EventProperty_JSON + } + } + + ret.Type.PropertyTypes = append(ret.Type.PropertyTypes, prop) + } + + em.Lock() + et, ok = em.metadata[key] + if ok { + // another write added the entry in the interim; discard ours. + em.Unlock() + ret.Type = et + return ret, nil + } + em.metadata[key] = ret.Type + em.Unlock() + return ret, nil +} diff --git a/introspect/event_manager_test.go b/introspect/event_manager_test.go new file mode 100644 index 0000000000..041a8569a0 --- /dev/null +++ b/introspect/event_manager_test.go @@ -0,0 +1,149 @@ +package introspect + +import ( + "testing" + "time" + + "github.com/multiformats/go-multiaddr" + "github.com/stretchr/testify/require" + + "github.com/libp2p/go-libp2p-core/event" + "github.com/libp2p/go-libp2p-core/introspection/pb" + "github.com/libp2p/go-libp2p-core/peer" +) + +type omnievent struct { + String string + Strings []string + Int int + Ints []int + RawJSON event.RawJSON + RawJSONs []event.RawJSON + PeerID peer.ID + PeerIDs []peer.ID + Time time.Time + Times []time.Time + Multiaddr multiaddr.Multiaddr + Multiaddrs []multiaddr.Multiaddr + Nested struct { + String string + Strings []string + Int int + Ints []int + RawJSON event.RawJSON + RawJSONs []event.RawJSON + PeerID peer.ID + PeerIDs []peer.ID + Time time.Time + Times []time.Time + Multiaddr multiaddr.Multiaddr + Multiaddrs []multiaddr.Multiaddr + } +} + +type EventA omnievent +type EventB omnievent + +func TestEventManager(t *testing.T) { + inCh := make(chan interface{}, 10) + em := newEventManager(inCh) + defer em.Close() + + require.Empty(t, em.EventMetadata()) + inCh <- EventA{} + + evt := <-em.EventChan() + require.NotNil(t, evt) + + compare := []struct { + Name string + Type pb.EventType_EventProperty_PropertyType + Multiple bool + }{ + {"String", pb.EventType_EventProperty_STRING, false}, + {"Strings", pb.EventType_EventProperty_STRING, true}, + {"Int", pb.EventType_EventProperty_NUMBER, false}, + {"Ints", pb.EventType_EventProperty_NUMBER, true}, + {"RawJSON", pb.EventType_EventProperty_JSON, false}, + {"RawJSONs", pb.EventType_EventProperty_JSON, true}, + {"PeerID", pb.EventType_EventProperty_PEERID, false}, + {"PeerIDs", pb.EventType_EventProperty_PEERID, true}, + {"Time", pb.EventType_EventProperty_TIME, false}, + {"Times", pb.EventType_EventProperty_TIME, true}, + {"Multiaddr", pb.EventType_EventProperty_MULTIADDR, false}, + {"Multiaddrs", pb.EventType_EventProperty_MULTIADDR, true}, + {"Nested", pb.EventType_EventProperty_JSON, false}, + } + + require.Equal(t, "EventA", evt.Type.Name) + + for i, pt := range evt.Type.PropertyTypes { + require.Equal(t, compare[i].Name, pt.Name) + require.Equal(t, compare[i].Type, pt.Type) + require.Equal(t, compare[i].Multiple, pt.HasMultiple) + } + + require.Len(t, em.EventMetadata(), 1) + require.Equal(t, evt.Type, em.EventMetadata()[0]) + + // send another event of type EventA; it should not inline the type definition. + inCh <- EventA{} + + evt = <-em.EventChan() + require.NotNil(t, evt) + require.Equal(t, "EventA", evt.Type.Name) + require.Nil(t, evt.Type.PropertyTypes) + + // send a new event; the type definition must be inlined. + inCh <- EventB{} + + evt = <-em.EventChan() + require.NotNil(t, evt) + require.Equal(t, "EventB", evt.Type.Name) + + for i, pt := range evt.Type.PropertyTypes { + require.Equal(t, compare[i].Name, pt.Name) + require.Equal(t, compare[i].Type, pt.Type) + require.Equal(t, compare[i].Multiple, pt.HasMultiple) + } + + require.Len(t, em.EventMetadata(), 2) +} + +func TestSubscriptionClosedClosesOut(t *testing.T) { + inCh := make(chan interface{}, 10) + em := newEventManager(inCh) + defer em.Close() + + require.Empty(t, em.EventMetadata()) + inCh <- EventA{} + + evt := <-em.EventChan() + require.NotNil(t, evt) + close(inCh) + + evt, more := <-em.EventChan() + require.Nil(t, evt) + require.False(t, more) +} + +func TestCloseStopsProcessing(t *testing.T) { + inCh := make(chan interface{}, 10) + em := newEventManager(inCh) + + require.Empty(t, em.EventMetadata()) + inCh <- EventA{} + evt := <-em.EventChan() + require.NotNil(t, evt) + + err := em.Close() + require.NoError(t, err) + + inCh <- EventA{} + inCh <- EventA{} + require.Len(t, inCh, 2) + + evt, more := <-em.EventChan() + require.Nil(t, evt) + require.False(t, more) +} diff --git a/introspect/mock_inspector.go b/introspect/mock_inspector.go new file mode 100644 index 0000000000..bbd7a0a371 --- /dev/null +++ b/introspect/mock_inspector.go @@ -0,0 +1,39 @@ +package introspect + +import ( + "github.com/stretchr/testify/mock" + + "github.com/libp2p/go-libp2p-core/introspection" + "github.com/libp2p/go-libp2p-core/introspection/pb" +) + +type MockIntrospector struct { + *eventManager + mock.Mock + + EventCh chan interface{} +} + +var _ introspection.Introspector = (*MockIntrospector)(nil) + +func NewMockIntrospector() *MockIntrospector { + mi := &MockIntrospector{ + EventCh: make(chan interface{}, 128), + } + mi.eventManager = newEventManager(mi.EventCh) + return mi +} + +func (m *MockIntrospector) Close() error { + return m.eventManager.Close() +} + +func (m *MockIntrospector) FetchRuntime() (*pb.Runtime, error) { + args := m.MethodCalled("FetchRuntime") + return args.Get(0).(*pb.Runtime), args.Error(1) +} + +func (m *MockIntrospector) FetchFullState() (*pb.State, error) { + args := m.MethodCalled("FetchFullState") + return args.Get(0).(*pb.State), args.Error(1) +} diff --git a/introspect/ws/server.go b/introspect/ws/server.go new file mode 100644 index 0000000000..532aa62f77 --- /dev/null +++ b/introspect/ws/server.go @@ -0,0 +1,351 @@ +package ws + +import ( + "encoding/binary" + "errors" + "fmt" + "hash/fnv" + "net" + "net/http" + "sync" + + "github.com/libp2p/go-libp2p-core/introspection" + "github.com/libp2p/go-libp2p-core/introspection/pb" + + "github.com/benbjohnson/clock" + "github.com/gorilla/websocket" + logging "github.com/ipfs/go-log" +) + +// ProtoVersion is the current version of the introspection protocol. +const ProtoVersion uint32 = 1 + +// ProtoVersionPb is the proto representation of the current introspection protocol. +var ProtoVersionPb = &pb.Version{Version: ProtoVersion} + +var ( + logger = logging.Logger("introspection/ws-server") + upgrader = websocket.Upgrader{} +) + +type sessionEvent struct { + session *session + doneCh chan struct{} +} + +type Endpoint struct { + // state initialized by constructor + introspector introspection.Introspector + config *EndpointConfig + server *http.Server + clock clock.Clock + + sessions map[*session]struct{} + + // state managed in the event loop + sessionOpenedCh chan *sessionEvent + sessionClosedCh chan *sessionEvent + getSessionsCh chan chan []*introspection.Session + stopConnsCh chan chan struct{} + + // state managed by locking + lk sync.RWMutex + listeners []net.Listener + + connsWg sync.WaitGroup + controlWg sync.WaitGroup + + closedCh chan struct{} + isClosed bool +} + +var _ introspection.Endpoint = (*Endpoint)(nil) + +type EndpointConfig struct { + ListenAddrs []string + Clock clock.Clock +} + +// EndpointWithConfig returns a function compatible with the +// libp2p.Introspection constructor option, which when called, creates an +// Endpoint with the supplied configuration. +func EndpointWithConfig(config *EndpointConfig) func(i introspection.Introspector) (introspection.Endpoint, error) { + return func(i introspection.Introspector) (introspection.Endpoint, error) { + return NewEndpoint(i, config) + } +} + +// NewEndpoint creates a WebSockets server to serve introspect data. +func NewEndpoint(introspector introspection.Introspector, config *EndpointConfig) (*Endpoint, error) { + if introspector == nil || config == nil { + return nil, errors.New("introspector and configuration can't be nil") + } + + mux := http.NewServeMux() + + srv := &Endpoint{ + introspector: introspector, + server: &http.Server{Handler: mux}, + config: config, + clock: config.Clock, + + sessions: make(map[*session]struct{}, 16), + + sessionOpenedCh: make(chan *sessionEvent), + sessionClosedCh: make(chan *sessionEvent), + stopConnsCh: make(chan chan struct{}), + getSessionsCh: make(chan chan []*introspection.Session), + + closedCh: make(chan struct{}), + } + + if srv.clock == nil { + // use the real clock. + srv.clock = clock.New() + } + + // register introspect session + mux.HandleFunc("/introspect", srv.wsUpgrader()) + return srv, nil +} + +// Start starts this WS server. +func (e *Endpoint) Start() error { + e.lk.Lock() + defer e.lk.Unlock() + + if len(e.listeners) > 0 { + return errors.New("failed to start WS server: already started") + } + if len(e.config.ListenAddrs) == 0 { + return errors.New("failed to start WS server: no listen addresses supplied") + } + + logger.Infof("WS introspection server starting, listening on %e", e.config.ListenAddrs) + + for _, addr := range e.config.ListenAddrs { + l, err := net.Listen("tcp", addr) + if err != nil { + return fmt.Errorf("failed to start WS server: %wsvc", err) + } + + go func() { + if err := e.server.Serve(l); err != http.ErrServerClosed { + logger.Errorf("failed to start WS server, err: %e", err) + } + }() + + e.listeners = append(e.listeners, l) + } + + // start the worker + e.controlWg.Add(1) + go e.worker() + + return nil +} + +// Close closes a WS introspect server. +func (e *Endpoint) Close() error { + e.lk.Lock() + defer e.lk.Unlock() + + if e.isClosed { + return nil + } + + ch := make(chan struct{}) + e.stopConnsCh <- ch + <-ch + + // wait for all connections to be dead. + e.connsWg.Wait() + + close(e.closedCh) + + // Close the server, which in turn closes all listeners. + if err := e.server.Close(); err != nil { + return err + } + + // cancel the context and wait for all goroutines to shut down + e.controlWg.Wait() + + e.listeners = nil + e.sessions = nil + e.isClosed = true + return nil +} + +// ListenAddrs returns the actual listen addresses of this server. +func (e *Endpoint) ListenAddrs() []string { + e.lk.RLock() + defer e.lk.RUnlock() + + res := make([]string, 0, len(e.listeners)) + for _, l := range e.listeners { + res = append(res, l.Addr().String()) + } + return res +} + +func (e *Endpoint) Sessions() []*introspection.Session { + ch := make(chan []*introspection.Session) + e.getSessionsCh <- ch + return <-ch +} + +func (e *Endpoint) wsUpgrader() http.HandlerFunc { + return func(w http.ResponseWriter, rq *http.Request) { + upgrader.CheckOrigin = func(rq *http.Request) bool { return true } + wsconn, err := upgrader.Upgrade(w, rq, nil) + if err != nil { + logger.Errorf("upgrade to websocket failed, err: %e", err) + return + } + + done := make(chan struct{}, 1) + select { + case e.sessionOpenedCh <- &sessionEvent{newSession(e, wsconn), done}: + case <-e.closedCh: + _ = wsconn.Close() + return + } + + select { + case <-done: + case <-e.closedCh: + _ = wsconn.Close() + return + } + } +} + +func (e *Endpoint) worker() { + defer e.controlWg.Done() + + eventCh := e.introspector.EventChan() + for { + select { + case rq := <-e.sessionOpenedCh: + session := rq.session + e.sessions[session] = struct{}{} + + e.connsWg.Add(1) + go func() { + session.run() + + select { + case e.sessionClosedCh <- &sessionEvent{session, rq.doneCh}: + case <-e.closedCh: + return + } + }() + + case rq := <-e.sessionClosedCh: + delete(e.sessions, rq.session) + e.connsWg.Done() + + case evt, more := <-eventCh: + if !more { + eventCh = nil + continue + } + + if len(e.sessions) == 0 { + continue + } + + // generate the event and broadcast it to all sessions. + if err := e.broadcastEvent(evt); err != nil { + logger.Warnf("error while broadcasting event; err: %e", err) + } + + case ch := <-e.getSessionsCh: + sessions := make([]*introspection.Session, 0, len(e.sessions)) + for sess := range e.sessions { + sessions = append(sessions, &introspection.Session{RemoteAddr: sess.wsconn.RemoteAddr().String()}) + } + ch <- sessions + + case ch := <-e.stopConnsCh: + // accept no more connections. + e.sessionOpenedCh = nil + for sess := range e.sessions { + sess.kill() + } + close(ch) + + case <-e.closedCh: + return + } + } +} + +func (e *Endpoint) broadcastEvent(evt *pb.Event) error { + pkt := &pb.ServerMessage{ + Version: ProtoVersionPb, + Payload: &pb.ServerMessage_Event{Event: evt}, + } + + msg, err := envelopePacket(pkt) + if err != nil { + return fmt.Errorf("failed to generate enveloped event message; err: %w", err) + } + + for sess := range e.sessions { + sess.trySendEvent(msg) + } + + return nil +} + +func (e *Endpoint) createStateMsg() ([]byte, error) { + st, err := e.introspector.FetchFullState() + if err != nil { + return nil, fmt.Errorf("failed to fetch state, err=%e", err) + } + + pkt := &pb.ServerMessage{ + Version: ProtoVersionPb, + Payload: &pb.ServerMessage_State{State: st}, + } + + return envelopePacket(pkt) +} + +func (e *Endpoint) createRuntimeMsg() ([]byte, error) { + rt, err := e.introspector.FetchRuntime() + if err != nil { + return nil, fmt.Errorf("failed to fetch runtime mesage, err=%e", err) + } + + rt.EventTypes = e.introspector.EventMetadata() + pkt := &pb.ServerMessage{ + Version: ProtoVersionPb, + Payload: &pb.ServerMessage_Runtime{Runtime: rt}, + } + + return envelopePacket(pkt) +} + +func envelopePacket(pkt *pb.ServerMessage) ([]byte, error) { + // TODO buffer pool. + size := pkt.Size() + buf := make([]byte, 12+size) + if _, err := pkt.MarshalToSizedBuffer(buf[12:]); err != nil { + return nil, err + } + + f := fnv.New32a() + _, err := f.Write(buf[12:]) + if err != nil { + return nil, fmt.Errorf("failed creating fnc hash digest, err: %w", err) + } + + binary.LittleEndian.PutUint32(buf[0:4], ProtoVersion) + binary.LittleEndian.PutUint32(buf[4:8], f.Sum32()) + binary.LittleEndian.PutUint32(buf[8:12], uint32(size)) + + return buf, nil +} diff --git a/introspect/ws/server_common_test.go b/introspect/ws/server_common_test.go new file mode 100644 index 0000000000..1d4bddbecc --- /dev/null +++ b/introspect/ws/server_common_test.go @@ -0,0 +1,107 @@ +package ws + +import ( + "encoding/binary" + "fmt" + "hash/fnv" + "testing" + + "github.com/libp2p/go-libp2p-core/introspection" + "github.com/libp2p/go-libp2p-core/introspection/pb" + + "github.com/libp2p/go-libp2p/introspect" + + "github.com/benbjohnson/clock" + "github.com/gorilla/websocket" + "github.com/stretchr/testify/require" +) + +func createTestServer(t *testing.T) (*Endpoint, *introspect.MockIntrospector, *clock.Mock) { + t.Helper() + + clk := clock.NewMock() + mocki := introspect.NewMockIntrospector() + cfg := &EndpointConfig{ListenAddrs: []string{"localhost:0"}, Clock: clk} + server, err := NewEndpoint(mocki, cfg) + require.NoError(t, err) + return server, mocki, clk +} + +type connWrapper struct { + *websocket.Conn + t *testing.T +} + +func createConn(t *testing.T, endpoint introspection.Endpoint) *connWrapper { + addr := fmt.Sprintf("ws://%s/introspect", endpoint.ListenAddrs()[0]) + conn, _, err := websocket.DefaultDialer.Dial(addr, nil) + require.NoError(t, err) + return &connWrapper{conn, t} +} + +func (cw *connWrapper) sendCommand(cmd *pb.ClientCommand) { + cw.t.Helper() + + msg, err := cmd.Marshal() + require.NoError(cw.t, err) + + err = cw.WriteMessage(websocket.BinaryMessage, msg) + require.NoError(cw.t, err) +} + +func (cw *connWrapper) greet() { + cw.t.Helper() + + cw.sendCommand(&pb.ClientCommand{Id: 200, Command: pb.ClientCommand_HELLO}) + + msg := cw.readNext() + resp := msg.Payload.(*pb.ServerMessage_Response).Response + + require.EqualValues(cw.t, 200, resp.Id) + require.EqualValues(cw.t, pb.CommandResponse_OK, resp.Result) + require.Empty(cw.t, resp.Error) +} + +func (cw *connWrapper) readNext() *pb.ServerMessage { + cw.t.Helper() + + _, msg, err := cw.ReadMessage() + require.NoError(cw.t, err) + + var ( + // parse the message + version = msg[0:4] + checksum = msg[4:8] + length = msg[8:12] + payload = msg[12:] + ) + + require.EqualValues(cw.t, ProtoVersion, binary.LittleEndian.Uint32(version)) + require.EqualValues(cw.t, len(payload), binary.LittleEndian.Uint32(length)) + + // validate hash. + h := fnv.New32a() + _, err = h.Write(payload) + require.NoError(cw.t, err) + require.EqualValues(cw.t, h.Sum32(), binary.LittleEndian.Uint32(checksum)) + + smsg := &pb.ServerMessage{} + + // read the protocol message directly + require.NoError(cw.t, smsg.Unmarshal(payload)) + + require.NotNil(cw.t, smsg.Payload, "nil message received from server") + require.Equal(cw.t, ProtoVersion, smsg.Version.Version, "incorrect proto version receieved from client") + + return smsg +} + +func (cw *connWrapper) consumeCommandResponse(id uint64, result pb.CommandResponse_Result) *pb.CommandResponse { + msg := cw.readNext() + resp := msg.Payload.(*pb.ServerMessage_Response).Response + + require.NotNil(cw.t, resp) + require.EqualValues(cw.t, id, resp.Id) + require.EqualValues(cw.t, result, resp.Result) + return resp +} diff --git a/introspect/ws/server_config_test.go b/introspect/ws/server_config_test.go new file mode 100644 index 0000000000..5c4c1f1b89 --- /dev/null +++ b/introspect/ws/server_config_test.go @@ -0,0 +1,90 @@ +package ws + +import ( + "testing" + + "github.com/libp2p/go-libp2p-core/introspection/pb" + + "github.com/stretchr/testify/require" +) + +func TestValidConfiguration(t *testing.T) { + server, _, _ := createTestServer(t) + require.NoError(t, server.Start()) + defer server.Close() + + conn := createConn(t, server) + defer conn.Close() + + config := &pb.Configuration{ + RetentionPeriodMs: uint64(MaxRetentionPeriod.Milliseconds() - 1), + StateSnapshotIntervalMs: uint64(MinStateSnapshotInterval.Milliseconds() + 1), + } + + // on HELLO + conn.sendCommand(&pb.ClientCommand{Id: 200, Command: pb.ClientCommand_HELLO, Config: config}) + + msg := conn.readNext() + resp := msg.Payload.(*pb.ServerMessage_Response).Response + + require.EqualValues(t, 200, resp.Id) + require.EqualValues(t, pb.CommandResponse_OK, resp.Result) + require.EqualValues(t, config, resp.EffectiveConfig) + require.Empty(t, resp.Error) + + // on UPDATE_VALUES, adjust the values to verify new values have been set. + config.RetentionPeriodMs -= 1 + config.StateSnapshotIntervalMs += 1 + conn.sendCommand(&pb.ClientCommand{Id: 201, Command: pb.ClientCommand_UPDATE_CONFIG, Config: config}) + + msg = conn.readNext() + resp = msg.Payload.(*pb.ServerMessage_Response).Response + + require.EqualValues(t, 201, resp.Id) + require.EqualValues(t, pb.CommandResponse_OK, resp.Result) + require.EqualValues(t, config, resp.EffectiveConfig) + require.Empty(t, resp.Error) +} + +func TestHelloWithInvalidConfig(t *testing.T) { + server, _, _ := createTestServer(t) + require.NoError(t, server.Start()) + defer server.Close() + + conn := createConn(t, server) + defer conn.Close() + + expected := &pb.Configuration{ + RetentionPeriodMs: uint64(MaxRetentionPeriod.Milliseconds()), + StateSnapshotIntervalMs: uint64(MinStateSnapshotInterval.Milliseconds()), + } + + config := &pb.Configuration{ + RetentionPeriodMs: uint64(MaxRetentionPeriod.Milliseconds() + 1), + StateSnapshotIntervalMs: uint64(MinStateSnapshotInterval.Milliseconds() - 1), + } + + // on HELLO + conn.sendCommand(&pb.ClientCommand{Id: 200, Command: pb.ClientCommand_HELLO, Config: config}) + + msg := conn.readNext() + resp := msg.Payload.(*pb.ServerMessage_Response).Response + + require.EqualValues(t, 200, resp.Id) + require.EqualValues(t, pb.CommandResponse_OK, resp.Result) + require.EqualValues(t, expected, resp.EffectiveConfig) + require.Empty(t, resp.Error) + + // on UPDATE_VALUES, adjust the values to verify new values have been set. + config.RetentionPeriodMs += 1 + config.StateSnapshotIntervalMs -= 1 + conn.sendCommand(&pb.ClientCommand{Id: 201, Command: pb.ClientCommand_UPDATE_CONFIG, Config: config}) + + msg = conn.readNext() + resp = msg.Payload.(*pb.ServerMessage_Response).Response + + require.EqualValues(t, 201, resp.Id) + require.EqualValues(t, pb.CommandResponse_OK, resp.Result) + require.EqualValues(t, expected, resp.EffectiveConfig) + require.Empty(t, resp.Error) +} diff --git a/introspect/ws/server_hello_test.go b/introspect/ws/server_hello_test.go new file mode 100644 index 0000000000..12a3b21ed0 --- /dev/null +++ b/introspect/ws/server_hello_test.go @@ -0,0 +1,56 @@ +package ws + +import ( + "testing" + + "github.com/libp2p/go-libp2p-core/introspection/pb" + + "github.com/stretchr/testify/require" +) + +func TestStartSession(t *testing.T) { + server, _, _ := createTestServer(t) + require.NoError(t, server.Start()) + defer server.Close() + + conn := createConn(t, server) + defer conn.Close() + + conn.greet() +} + +func TestDoubleHelloFails(t *testing.T) { + server, _, _ := createTestServer(t) + require.NoError(t, server.Start()) + defer server.Close() + + conn := createConn(t, server) + defer conn.Close() + + conn.greet() + + conn.sendCommand(&pb.ClientCommand{Id: 201, Command: pb.ClientCommand_HELLO}) + + msg := conn.readNext() + resp := msg.Payload.(*pb.ServerMessage_Response).Response + + require.EqualValues(t, 201, resp.Id) + require.EqualValues(t, pb.CommandResponse_ERR, resp.Result) +} + +func TestNoHelloFails(t *testing.T) { + server, _, _ := createTestServer(t) + require.NoError(t, server.Start()) + defer server.Close() + + conn := createConn(t, server) + defer conn.Close() + + conn.sendCommand(&pb.ClientCommand{Id: 200, Command: pb.ClientCommand_REQUEST, Source: pb.ClientCommand_RUNTIME}) + + msg := conn.readNext() + resp := msg.Payload.(*pb.ServerMessage_Response).Response + + require.EqualValues(t, 200, resp.Id) + require.EqualValues(t, pb.CommandResponse_ERR, resp.Result) +} diff --git a/introspect/ws/server_integration_test.go b/introspect/ws/server_integration_test.go new file mode 100644 index 0000000000..bc00c0b5e8 --- /dev/null +++ b/introspect/ws/server_integration_test.go @@ -0,0 +1,191 @@ +package ws + +import ( + "context" + "io" + "runtime" + "testing" + + "github.com/libp2p/go-libp2p-core/host" + "github.com/libp2p/go-libp2p-core/introspection/pb" + "github.com/libp2p/go-libp2p-core/metrics" + "github.com/libp2p/go-libp2p-core/network" + "github.com/libp2p/go-libp2p-core/peer" + "github.com/libp2p/go-libp2p-core/protocol" + + "github.com/libp2p/go-libp2p" + "github.com/libp2p/go-libp2p/introspect" + + "github.com/stretchr/testify/require" +) + +var ( + msg1 = []byte("1") + msg2 = []byte("12") + msg3 = []byte("111") + msg4 = []byte("0000") + + p1 = protocol.ID("h1h3") + p2 = protocol.ID("h2h1") +) + +func TestHostIntrospection(t *testing.T) { + require := require.New(t) + + iaddr := "127.0.0.1:0" + ctx := context.Background() + + // create host 1 with introspect + h1, err := libp2p.New(ctx, + libp2p.Introspection( + introspect.NewDefaultIntrospector, + EndpointWithConfig(&EndpointConfig{ListenAddrs: []string{iaddr}}), + ), + libp2p.BandwidthReporter(metrics.NewBandwidthCounter()), + ) + require.NoError(err) + defer h1.Close() + + // create host 2 + h2, err := libp2p.New(ctx) + defer h2.Close() + + // create host 3 + h3, err := libp2p.New(ctx) + defer h3.Close() + + // host1 -> CONNECTS -> host2 + require.NoError(h1.Connect(ctx, peer.AddrInfo{ID: h2.ID(), Addrs: h2.Addrs()})) + + // host3 -> CONNECTS -> host1 + require.NoError(h3.Connect(ctx, peer.AddrInfo{ID: h1.ID(), Addrs: h1.Addrs()})) + + // host1 -> OPENS STREAM 1 -> host3, Writes a message & then reads the response + h3.SetStreamHandler(p1, func(s network.Stream) { + buf := make([]byte, len(msg1)) + + _, err := io.ReadFull(s, buf) + require.NoError(err) + + _, err = s.Write(msg2) + require.NoError(err) + }) + + s1, err := h1.NewStream(ctx, h3.ID(), p1) + require.NoError(err) + + _, err = s1.Write(msg1) + require.NoError(err) + + buf := make([]byte, len(msg2)) + _, err = io.ReadFull(s1, buf) + require.NoError(err) + + // host2 -> OPENS Stream 2 -> host1 , writes a message & reads the response + h1.SetStreamHandler(p2, func(s network.Stream) { + buf := make([]byte, len(msg3)) + + _, err := io.ReadFull(s, buf) + require.NoError(err) + + _, err = s.Write(msg4) + require.NoError(err) + }) + + s2, err := h2.NewStream(ctx, h1.ID(), p2) + require.NoError(err) + + _, err = s2.Write(msg3) + require.NoError(err) + + buf = make([]byte, len(msg4)) + _, err = io.ReadFull(s2, buf) + require.NoError(err) + + conn := createConn(t, h1.(host.IntrospectableHost).IntrospectionEndpoint()) + defer conn.Close() + + // first, we get the runtime and verify it. + conn.greet() + + conn.sendCommand(&pb.ClientCommand{Id: 200, Command: pb.ClientCommand_REQUEST, Source: pb.ClientCommand_RUNTIME}) + msg := conn.readNext() + rt := msg.GetRuntime() + require.NotNil(t, rt) + require.Equal(h1.ID().String(), rt.PeerId) + require.Equal(runtime.GOOS, rt.Platform) + require.Equal("go-libp2p", rt.Implementation) + + // now we get the state. + conn.sendCommand(&pb.ClientCommand{Id: 200, Command: pb.ClientCommand_REQUEST, Source: pb.ClientCommand_STATE}) + msg = conn.readNext() + st := msg.GetState() + require.NotNil(t, st) + assertState(require, st, h1, h2, h3, false) +} + +func assertState(require *require.Assertions, state *pb.State, h1, h2, h3 host.Host, + assertTraffic bool) { + + if assertTraffic { + require.Greater(state.Traffic.TrafficIn.CumBytes, uint64(100)) + require.Greater(state.Traffic.TrafficOut.CumBytes, uint64(100)) + } + + // Connections + conns := state.Subsystems.Connections + peerIdToConns := make(map[string]*pb.Connection) + for _, c := range conns { + peerIdToConns[c.PeerId] = c + } + require.Len(peerIdToConns, 2) + + pconn := make(map[string]network.Conn) + for _, c := range h1.Network().Conns() { + pconn[c.RemotePeer().String()] = c + } + require.Len(pconn, 2) + + // host1 -> host2 connection + h2Conn := peerIdToConns[h2.ID().String()] + require.NotEmpty(h2Conn.Id) + require.Equal(pb.Status_ACTIVE, h2Conn.Status) + require.Equal(pconn[h2.ID().String()].LocalMultiaddr().String(), h2Conn.Endpoints.SrcMultiaddr) + require.Equal(pconn[h2.ID().String()].RemoteMultiaddr().String(), h2Conn.Endpoints.DstMultiaddr) + require.Equal(pb.Role_INITIATOR, h2Conn.Role) + + if assertTraffic { + require.Greater(h2Conn.Traffic.TrafficIn.CumBytes, uint64(len(msg3))) + } + + // host3 -> host1 connection + h3Conn := peerIdToConns[h3.ID().String()] + require.NotEmpty(h3Conn.Id) + require.Equal(pb.Status_ACTIVE, h3Conn.Status) + require.Equal(pconn[h3.ID().String()].LocalMultiaddr().String(), h3Conn.Endpoints.SrcMultiaddr) + require.Equal(pconn[h3.ID().String()].RemoteMultiaddr().String(), h3Conn.Endpoints.DstMultiaddr) + require.Equal(pb.Role_RESPONDER, h3Conn.Role) + + if assertTraffic { + require.Greater(h3Conn.Traffic.TrafficIn.CumBytes, uint64(len(msg2))) + require.Greater(h3Conn.Traffic.TrafficOut.CumBytes, uint64(len(msg1))) + } + // stream1 + require.Len(h3Conn.Streams.Streams, 1) + h3Stream := h3Conn.Streams.Streams[0] + require.NotEmpty(h3Stream.Id) + require.Equal(string(p1), h3Stream.Protocol) + require.Equal(pb.Role_INITIATOR, h3Stream.Role) + require.Equal(pb.Status_ACTIVE, h3Stream.Status) + // require.True(len(msg1) == int(h3Stream.Traffic.TrafficOut.CumBytes)) + // require.True(len(msg2) == int(h3Stream.Traffic.TrafficIn.CumBytes)) + + // stream 2 + require.Len(h2Conn.Streams.Streams, 1) + h1Stream := h2Conn.Streams.Streams[0] + require.NotEmpty(h1Stream.Id) + require.Equal(string(p2), h1Stream.Protocol) + require.Equal(pb.Role_RESPONDER, h1Stream.Role) + require.Equal(pb.Status_ACTIVE, h1Stream.Status) + // require.True(len(msg3) == int(h1Stream.Traffic.TrafficIn.CumBytes)) +} diff --git a/introspect/ws/server_lifecycle_test.go b/introspect/ws/server_lifecycle_test.go new file mode 100644 index 0000000000..bcea11080a --- /dev/null +++ b/introspect/ws/server_lifecycle_test.go @@ -0,0 +1,60 @@ +package ws + +import ( + "io" + "strings" + "sync" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +func TestServerClose(t *testing.T) { + server, _, _ := createTestServer(t) + err := server.Start() + require.NoError(t, err) + + // create 100 connections. + var wg sync.WaitGroup + conns := make([]*connWrapper, 100) + wg.Add(100) + for i := range conns { + conn := createConn(t, server) + go func() { + _, _, err := conn.ReadMessage() + if strings.Contains(err.Error(), io.ErrUnexpectedEOF.Error()) { + wg.Done() + } + }() + conns[i] = conn + } + + err = server.Close() + require.NoError(t, err) + wg.Wait() +} + +func TestServerHandlesClosedConns(t *testing.T) { + server, _, _ := createTestServer(t) + defer server.Close() + + require.NoError(t, server.Start()) + + conns := make([]*connWrapper, 50) + for i := range conns { + conn := createConn(t, server) + conns[i] = conn + } + + require.Eventually(t, func() bool { return len(server.Sessions()) == 50 }, 2*time.Second, 100*time.Millisecond) + + err := conns[0].Close() + require.NoError(t, err) + + require.Eventually(t, func() bool { return len(server.Sessions()) == 49 }, 2*time.Second, 100*time.Millisecond) + + for _, c := range conns[1:] { + _ = c.Close() + } +} diff --git a/introspect/ws/server_push_test.go b/introspect/ws/server_push_test.go new file mode 100644 index 0000000000..724f9acf20 --- /dev/null +++ b/introspect/ws/server_push_test.go @@ -0,0 +1,272 @@ +package ws + +import ( + "testing" + "time" + + "github.com/libp2p/go-libp2p-core/introspection/pb" + + "github.com/stretchr/testify/require" +) + +type simpleEvent struct { + String string + Number int +} + +type EventA simpleEvent +type EventB simpleEvent +type EventC simpleEvent + +func TestPushEvents(t *testing.T) { + server, mocki, _ := createTestServer(t) + require.NoError(t, server.Start()) + defer server.Close() + + conn := createConn(t, server) + defer conn.Close() + + conn.greet() + conn.sendCommand(&pb.ClientCommand{Id: 201, Command: pb.ClientCommand_PUSH_ENABLE, Source: pb.ClientCommand_EVENTS}) + + msg := conn.readNext() + resp := msg.Payload.(*pb.ServerMessage_Response).Response + + require.EqualValues(t, 201, resp.Id) + require.EqualValues(t, pb.CommandResponse_OK, resp.Result) + + assertEvent := func(name string) { + msg := conn.readNext() + evt := msg.Payload.(*pb.ServerMessage_Event).Event + + require.EqualValues(t, name, evt.Type.Name) + require.NotEmpty(t, evt.Content) + require.NotZero(t, evt.Ts) + } + + mocki.EventCh <- EventA{String: "hello", Number: 100} + mocki.EventCh <- EventA{String: "hello", Number: 100} + mocki.EventCh <- EventB{String: "hello", Number: 100} + + assertEvent("EventA") + assertEvent("EventA") + assertEvent("EventB") +} + +func TestPushStopPushEvents(t *testing.T) { + server, mocki, _ := createTestServer(t) + require.NoError(t, server.Start()) + defer server.Close() + + conn := createConn(t, server) + defer conn.Close() + + conn.greet() + conn.sendCommand(&pb.ClientCommand{Id: 201, Command: pb.ClientCommand_PUSH_ENABLE, Source: pb.ClientCommand_EVENTS}) + + msg := conn.readNext() + resp := msg.Payload.(*pb.ServerMessage_Response).Response + + require.EqualValues(t, 201, resp.Id) + require.EqualValues(t, pb.CommandResponse_OK, resp.Result) + + assertEvent := func(name string) { + msg := conn.readNext() + evt := msg.Payload.(*pb.ServerMessage_Event).Event + + require.EqualValues(t, name, evt.Type.Name) + require.NotEmpty(t, evt.Content) + require.NotZero(t, evt.Ts) + } + + mocki.EventCh <- EventA{String: "hello", Number: 100} + assertEvent("EventA") + + // now disable the pusher and verify that we actually missed those events. + conn.sendCommand(&pb.ClientCommand{Id: 201, Command: pb.ClientCommand_PUSH_DISABLE, Source: pb.ClientCommand_EVENTS}) + msg = conn.readNext() + resp = msg.Payload.(*pb.ServerMessage_Response).Response + require.EqualValues(t, 201, resp.Id) + require.EqualValues(t, pb.CommandResponse_OK, resp.Result) + + time.Sleep(1 * time.Second) + + // these events will be missed. + mocki.EventCh <- EventA{String: "hello", Number: 100} + mocki.EventCh <- EventB{String: "hello", Number: 100} + time.Sleep(500 * time.Millisecond) + + // enable the pusher again + conn.sendCommand(&pb.ClientCommand{Id: 201, Command: pb.ClientCommand_PUSH_ENABLE, Source: pb.ClientCommand_EVENTS}) + msg = conn.readNext() + resp = msg.Payload.(*pb.ServerMessage_Response).Response + require.EqualValues(t, 201, resp.Id) + require.EqualValues(t, pb.CommandResponse_OK, resp.Result) + + // these events will be received. + mocki.EventCh <- EventC{String: "hello", Number: 100} + mocki.EventCh <- EventC{String: "hello", Number: 100} + + assertEvent("EventC") + assertEvent("EventC") +} + +func TestPushEventsIdempotent(t *testing.T) { + server, _, _ := createTestServer(t) + require.NoError(t, server.Start()) + defer server.Close() + + conn := createConn(t, server) + defer conn.Close() + + conn.greet() + conn.sendCommand(&pb.ClientCommand{Id: 201, Command: pb.ClientCommand_PUSH_ENABLE, Source: pb.ClientCommand_EVENTS}) + + msg := conn.readNext() + resp := msg.Payload.(*pb.ServerMessage_Response).Response + + require.EqualValues(t, 201, resp.Id) + require.EqualValues(t, pb.CommandResponse_OK, resp.Result) + + conn.sendCommand(&pb.ClientCommand{Id: 202, Command: pb.ClientCommand_PUSH_ENABLE, Source: pb.ClientCommand_EVENTS}) + + msg = conn.readNext() + resp = msg.Payload.(*pb.ServerMessage_Response).Response + + require.EqualValues(t, 202, resp.Id) + require.EqualValues(t, pb.CommandResponse_OK, resp.Result) +} + +func TestPauseResume(t *testing.T) { + server, mocki, clk := createTestServer(t) + require.NoError(t, server.Start()) + defer server.Close() + + mocki.On("FetchFullState").Return(&pb.State{}, nil) + + conn := createConn(t, server) + defer conn.Close() + + conn.greet() + + conn.sendCommand(&pb.ClientCommand{Id: 201, Command: pb.ClientCommand_PUSH_ENABLE, Source: pb.ClientCommand_EVENTS}) + conn.sendCommand(&pb.ClientCommand{Id: 202, Command: pb.ClientCommand_PUSH_ENABLE, Source: pb.ClientCommand_STATE}) + conn.sendCommand(&pb.ClientCommand{Id: 203, Command: pb.ClientCommand_PUSH_PAUSE}) + + conn.consumeCommandResponse(201, pb.CommandResponse_OK) + conn.consumeCommandResponse(202, pb.CommandResponse_OK) + conn.consumeCommandResponse(203, pb.CommandResponse_OK) + + type tsmsg struct { + ts time.Time + msg *pb.ServerMessage + } + + var rcvd []tsmsg + triggerDrain, rcvdAll := make(chan struct{}), make(chan struct{}) + go func() { + defer close(rcvdAll) + <-triggerDrain + for i := 0; i < 9; i++ { + m := tsmsg{clk.Now(), conn.readNext()} + rcvd = append(rcvd, m) + } + }() + + // tick 5 seconds; this will generate 5 state messages. + clk.Add(5 * time.Second) + + // send one event, tick once (state), send one event, tick once (state) + mocki.EventCh <- EventA{String: "hello", Number: 100} + clk.Add(time.Second) + mocki.EventCh <- EventB{String: "hello", Number: 100} + clk.Add(time.Second) + + // now resume, we should receive 9 messages all after now. + now := clk.Now() + conn.sendCommand(&pb.ClientCommand{Id: 204, Command: pb.ClientCommand_PUSH_RESUME}) + conn.consumeCommandResponse(204, pb.CommandResponse_OK) + close(triggerDrain) + + // we should be having 9 messages. + <-rcvdAll + + // all messages were received _after_ we resumed. + for _, m := range rcvd { + require.True(t, m.ts.Equal(now) || m.ts.After(now)) + } +} + +func TestPauseResumeMessagesDropped(t *testing.T) { + server, mocki, clk := createTestServer(t) + require.NoError(t, server.Start()) + defer server.Close() + + mocki.On("FetchFullState").Return(&pb.State{}, nil) + + conn := createConn(t, server) + defer conn.Close() + + conn.greet() + + config := DefaultSessionConfig + config.RetentionPeriodMs = 2000 // 2 seconds retention period. + + conn.sendCommand(&pb.ClientCommand{Id: 201, Command: pb.ClientCommand_PUSH_ENABLE, Source: pb.ClientCommand_EVENTS}) + conn.sendCommand(&pb.ClientCommand{Id: 202, Command: pb.ClientCommand_PUSH_ENABLE, Source: pb.ClientCommand_STATE}) + conn.sendCommand(&pb.ClientCommand{Id: 203, Command: pb.ClientCommand_UPDATE_CONFIG, Config: &config}) + conn.sendCommand(&pb.ClientCommand{Id: 204, Command: pb.ClientCommand_PUSH_PAUSE}) + + conn.consumeCommandResponse(201, pb.CommandResponse_OK) + conn.consumeCommandResponse(202, pb.CommandResponse_OK) + conn.consumeCommandResponse(203, pb.CommandResponse_OK) + conn.consumeCommandResponse(204, pb.CommandResponse_OK) + + type tsmsg struct { + ts time.Time + msg *pb.ServerMessage + } + + var rcvd []tsmsg + triggerDrain, rcvdAll := make(chan struct{}), make(chan struct{}) + go func() { + defer close(rcvdAll) + <-triggerDrain + for i := 0; i < 5; i++ { + m := tsmsg{clk.Now(), conn.readNext()} + rcvd = append(rcvd, m) + } + }() + + // the event and the first few state messages will be discarded because they slid out of the retention window. + clk.Add(5 * time.Second) + mocki.EventCh <- EventA{String: "hello", Number: 100} + clk.Add(5 * time.Second) + + // send one event, tick once (state), send one event, tick once (state) + mocki.EventCh <- EventB{String: "hello", Number: 100} + mocki.EventCh <- EventB{String: "hello", Number: 100} + + // now resume, we should receive 9 messages all after now. + now := clk.Now() + conn.sendCommand(&pb.ClientCommand{Id: 205, Command: pb.ClientCommand_PUSH_RESUME}) + conn.consumeCommandResponse(205, pb.CommandResponse_OK) + close(triggerDrain) + + // we should be having 5 messages. + <-rcvdAll + + // all messages were received _after_ we resumed. + for _, m := range rcvd { + require.True(t, m.ts.Equal(now) || m.ts.After(now)) + } + + // three state messages + two events of type EventB. + require.NotNil(t, rcvd[0].msg.Payload.(*pb.ServerMessage_State).State) + require.NotNil(t, rcvd[1].msg.Payload.(*pb.ServerMessage_State).State) + require.NotNil(t, rcvd[2].msg.Payload.(*pb.ServerMessage_State).State) + require.NotNil(t, rcvd[3].msg.Payload.(*pb.ServerMessage_Event).Event) + require.NotNil(t, rcvd[4].msg.Payload.(*pb.ServerMessage_Event).Event) + require.EqualValues(t, "EventB", rcvd[3].msg.Payload.(*pb.ServerMessage_Event).Event.Type.Name) + require.EqualValues(t, "EventB", rcvd[4].msg.Payload.(*pb.ServerMessage_Event).Event.Type.Name) +} diff --git a/introspect/ws/server_request_test.go b/introspect/ws/server_request_test.go new file mode 100644 index 0000000000..3b72acfe8d --- /dev/null +++ b/introspect/ws/server_request_test.go @@ -0,0 +1,64 @@ +package ws + +import ( + "testing" + + "github.com/libp2p/go-libp2p-core/introspection/pb" + + "github.com/stretchr/testify/require" +) + +func TestRequestState(t *testing.T) { + server, mocki, _ := createTestServer(t) + require.NoError(t, server.Start()) + defer server.Close() + + conn := createConn(t, server) + defer conn.Close() + + conn.greet() + + mocki.On("FetchFullState").Return(&pb.State{}, nil) + conn.sendCommand(&pb.ClientCommand{Id: 201, Command: pb.ClientCommand_REQUEST, Source: pb.ClientCommand_STATE}) + + msg := conn.readNext() + require.NotNil(t, msg.Payload.(*pb.ServerMessage_State).State) + mocki.AssertNumberOfCalls(t, "FetchFullState", 1) +} + +func TestRequestRuntime(t *testing.T) { + server, mocki, _ := createTestServer(t) + require.NoError(t, server.Start()) + defer server.Close() + + conn := createConn(t, server) + defer conn.Close() + + conn.greet() + + mocki.On("FetchRuntime").Return(&pb.Runtime{}, nil) + conn.sendCommand(&pb.ClientCommand{Id: 201, Command: pb.ClientCommand_REQUEST, Source: pb.ClientCommand_RUNTIME}) + + msg := conn.readNext() + require.NotNil(t, msg.Payload.(*pb.ServerMessage_Runtime).Runtime) + mocki.AssertNumberOfCalls(t, "FetchRuntime", 1) +} + +func TestRequestEventsFails(t *testing.T) { + server, _, _ := createTestServer(t) + require.NoError(t, server.Start()) + defer server.Close() + + conn := createConn(t, server) + defer conn.Close() + + conn.greet() + + conn.sendCommand(&pb.ClientCommand{Id: 201, Command: pb.ClientCommand_REQUEST, Source: pb.ClientCommand_EVENTS}) + + msg := conn.readNext() + resp := msg.Payload.(*pb.ServerMessage_Response).Response + + require.EqualValues(t, 201, resp.Id) + require.EqualValues(t, pb.CommandResponse_ERR, resp.Result) +} diff --git a/introspect/ws/session.go b/introspect/ws/session.go new file mode 100644 index 0000000000..ac58575f7a --- /dev/null +++ b/introspect/ws/session.go @@ -0,0 +1,426 @@ +package ws + +import ( + "fmt" + "sync" + "sync/atomic" + "time" + + "github.com/libp2p/go-libp2p-core/introspection/pb" + + "github.com/benbjohnson/clock" + "github.com/gorilla/websocket" + "go.uber.org/zap" +) + +var handlers = map[pb.ClientCommand_Command]func(*session, *pb.ClientCommand) *pb.ServerMessage{ + pb.ClientCommand_HELLO: (*session).handleHelloCmd, + pb.ClientCommand_REQUEST: (*session).handleRequestCmd, + pb.ClientCommand_PUSH_ENABLE: (*session).handlePushEnableCmd, + pb.ClientCommand_PUSH_DISABLE: (*session).handlePushDisableCmd, + pb.ClientCommand_PUSH_PAUSE: (*session).handlePushPauseCmd, + pb.ClientCommand_PUSH_RESUME: (*session).handlePushResumeCmd, + pb.ClientCommand_UPDATE_CONFIG: (*session).handleUpdateConfigCmd, +} + +var ( + MaxRetentionPeriod = 120 * time.Second + MinStateSnapshotInterval = 500 * time.Millisecond + PruneRetentionInterval = 2 * time.Second + + WriteTimeout = 5 * time.Second + ConnBufferSize = 1 << 8 + + DefaultSessionConfig = pb.Configuration{ + RetentionPeriodMs: uint64(MaxRetentionPeriod / time.Millisecond), + StateSnapshotIntervalMs: uint64(time.Second / time.Millisecond), + } +) + +type qitem struct { + ts time.Time + payload []byte +} + +type session struct { + server *Endpoint + wsconn *websocket.Conn + logger *zap.SugaredLogger + writeCh chan []byte + + pushingEvents bool + pushingState bool + paused bool + + eventCh chan []byte + commandCh chan *pb.ClientCommand + + stateTicker *clock.Ticker + + greeted bool + config *pb.Configuration + q []*qitem + + wg sync.WaitGroup + closed int32 + closeCh chan struct{} +} + +func newSession(sv *Endpoint, wsconn *websocket.Conn) *session { + cfgcpy := DefaultSessionConfig + ch := &session{ + server: sv, + wsconn: wsconn, + config: &cfgcpy, + stateTicker: new(clock.Ticker), + + writeCh: make(chan []byte, ConnBufferSize), + eventCh: make(chan []byte, ConnBufferSize), + commandCh: make(chan *pb.ClientCommand), + closeCh: make(chan struct{}), + } + + ch.logger = logger.Named(wsconn.RemoteAddr().String()) + return ch +} + +func (s *session) run() { + s.wg.Add(3) + + go s.writeLoop() + go s.readLoop() + go s.control() + + s.wg.Wait() +} + +func (s *session) trySendEvent(msg []byte) { + select { + case s.eventCh <- msg: + case <-s.closeCh: + default: + s.logger.Warnf("unable to queue event; dropping") + } +} + +func (s *session) queueWrite(msg []byte) { + select { + case s.writeCh <- msg: + case <-s.closeCh: + s.logger.Warnf("dropping queued message upon close") + } +} + +func (s *session) control() { + defer s.wg.Done() + + // dummy ticker that won't tick unless enabled. + pruneQTicker := s.server.clock.Ticker(PruneRetentionInterval) + defer pruneQTicker.Stop() + defer func() { + if s.pushingState { + s.stateTicker.Stop() + } + }() + + for { + select { + case <-s.stateTicker.C: + msg, err := s.server.createStateMsg() + if err != nil { + s.logger.Warnf("failed to generate state message on tick; err: %s", err) + continue + } + if s.paused { + s.q = append(s.q, &qitem{s.server.clock.Now(), msg}) + continue + } + s.queueWrite(msg) + + case now := <-pruneQTicker.C: + if !s.paused || len(s.q) == 0 { + continue + } + + i := 0 + thres := now.Add(-time.Duration(s.config.RetentionPeriodMs) * time.Millisecond) + for ; i < len(s.q) && s.q[i].ts.Before(thres); i++ { + } + s.q = s.q[i:] + + case evt := <-s.eventCh: + if !s.pushingEvents { + continue + } + if s.paused { + s.q = append(s.q, &qitem{s.server.clock.Now(), evt}) + continue + } + s.queueWrite(evt) + + case cmd := <-s.commandCh: + var resp *pb.ServerMessage + handler, ok := handlers[cmd.Command] + if !ok { + err := fmt.Errorf("unknown command type: %v", cmd.Command) + resp = createCmdErrorResp(cmd, err) + s.logger.Warnf("%s", err) + } else { + resp = handler(s, cmd) + } + + if resp != nil { + msg, err := envelopePacket(resp) + if err != nil { + s.logger.Warnf("failed to marshal client message; err: %s", err) + s.kill() + return + } + s.queueWrite(msg) + } + + case <-s.closeCh: + s.q = nil + return + } + } +} + +func (s *session) writeLoop() { + defer s.wg.Done() + + for { + select { + case msg := <-s.writeCh: + _ = s.wsconn.SetWriteDeadline(time.Now().Add(WriteTimeout)) + if err := s.wsconn.WriteMessage(websocket.BinaryMessage, msg); err != nil { + s.logger.Warnf("failed to send binary message to client with addr %s, err=%s", err) + s.kill() + return + } + + case <-s.closeCh: + return + } + } +} + +func (s *session) kill() { + if atomic.SwapInt32(&s.closed, 1) == 0 { + close(s.closeCh) + _ = s.wsconn.Close() + } +} + +func (s *session) readLoop() { + defer s.wg.Done() + + for { + mt, message, err := s.wsconn.ReadMessage() + switch err.(type) { + case nil: + case *websocket.CloseError: + s.logger.Warnf("connection closed; err: %s", err) + s.kill() + return + default: + s.logger.Warnf("failed to read message from ws connection; err: %s", err) + s.kill() + return + } + + s.logger.Debugf("received message from ws connection; type: %d; recv: %x", mt, message) + + cmd := new(pb.ClientCommand) + if err := cmd.Unmarshal(message); err != nil { + s.logger.Warnf("failed to read client message; err: %s", err) + s.kill() + return + } + + select { + case s.commandCh <- cmd: + case <-s.closeCh: + return + } + } +} + +func (s *session) handleHelloCmd(cmd *pb.ClientCommand) *pb.ServerMessage { + if s.greeted { + return createCmdErrorResp(cmd, fmt.Errorf("client had already greeted server")) + } + s.greeted = true + if cmd.Config != nil { + s.config = s.validateConfig(*cmd.Config) + } + resp := createCmdOKResp(cmd) + resp.Payload.(*pb.ServerMessage_Response).Response.EffectiveConfig = s.config + return resp +} + +func (s *session) handleRequestCmd(cmd *pb.ClientCommand) *pb.ServerMessage { + if !s.greeted { + return createCmdErrorResp(cmd, fmt.Errorf("client has not greeted server yet")) + } + + var ( + bytes []byte + err error + ) + switch cmd.Source { + case pb.ClientCommand_EVENTS: + err = fmt.Errorf("illegal request for events messages") + case pb.ClientCommand_RUNTIME: + bytes, err = s.server.createRuntimeMsg() + case pb.ClientCommand_STATE: + bytes, err = s.server.createStateMsg() + } + if err != nil { + return createCmdErrorResp(cmd, err) + } + s.writeCh <- bytes + return nil // response is the actual requested payload +} + +func (s *session) handlePushEnableCmd(cmd *pb.ClientCommand) *pb.ServerMessage { + if !s.greeted { + return createCmdErrorResp(cmd, fmt.Errorf("client has not greeted server yet")) + } + + switch cmd.Source { + case pb.ClientCommand_STATE: + if s.pushingState { + break // do nothing + } + s.pushingState = true + s.stateTicker = s.server.clock.Ticker(time.Duration(s.config.StateSnapshotIntervalMs) * time.Millisecond) + case pb.ClientCommand_EVENTS: + s.pushingEvents = true + default: + return createCmdErrorResp(cmd, fmt.Errorf("specified source does not support pushing")) + } + return createCmdOKResp(cmd) +} + +func (s *session) handlePushDisableCmd(cmd *pb.ClientCommand) *pb.ServerMessage { + if !s.greeted { + return createCmdErrorResp(cmd, fmt.Errorf("client has not greeted server yet")) + } + + switch cmd.Source { + case pb.ClientCommand_STATE: + if !s.pushingState { + break // do nothing + } + s.pushingState = false + s.stateTicker.Stop() + case pb.ClientCommand_EVENTS: + s.pushingEvents = false + default: + return createCmdErrorResp(cmd, fmt.Errorf("specified source does not support pushing")) + } + + // if all pushers are disabled, clear the queue. + if !s.pushingState && !s.pushingEvents { + s.q = nil + } + + return createCmdOKResp(cmd) +} + +func (s *session) handlePushPauseCmd(cmd *pb.ClientCommand) *pb.ServerMessage { + if !s.greeted { + return createCmdErrorResp(cmd, fmt.Errorf("client has not greeted server yet")) + } + + s.paused = true + return createCmdOKResp(cmd) +} + +func (s *session) handlePushResumeCmd(cmd *pb.ClientCommand) *pb.ServerMessage { + if !s.greeted { + return createCmdErrorResp(cmd, fmt.Errorf("client has not greeted server yet")) + } + + if !s.paused { + // if we are not paused, there's nothing to do. + return createCmdOKResp(cmd) + } + + msg := createCmdOKResp(cmd) + bytes, err := envelopePacket(msg) + if err != nil { + s.logger.Warnf("failed to marshal client message; err: %s", err) + s.kill() + return nil + } + + s.queueWrite(bytes) + for _, msg := range s.q { + s.queueWrite(msg.payload) + } + + s.q = nil + s.paused = false + return nil +} + +func (s *session) handleUpdateConfigCmd(cmd *pb.ClientCommand) *pb.ServerMessage { + if !s.greeted { + return createCmdErrorResp(cmd, fmt.Errorf("client has not greeted server yet")) + } + + if cmd.Config == nil { + return createCmdErrorResp(cmd, fmt.Errorf("client passed nil configuration")) + } + + old, neu := s.config, cmd.Config + neu = s.validateConfig(*neu) + + if s.pushingState && old.StateSnapshotIntervalMs != neu.StateSnapshotIntervalMs { + // reset the state ticker to the new interval, if we're pushing state. + s.stateTicker.Stop() + s.stateTicker = s.server.clock.Ticker(time.Duration(neu.StateSnapshotIntervalMs) * time.Millisecond) + } + + s.config = neu + resp := createCmdOKResp(cmd) + resp.Payload.(*pb.ServerMessage_Response).Response.EffectiveConfig = s.config + return resp +} + +func (s *session) validateConfig(config pb.Configuration) *pb.Configuration { + if min := uint64(MinStateSnapshotInterval.Milliseconds()); config.StateSnapshotIntervalMs < min { + config.StateSnapshotIntervalMs = min + } + if max := uint64(MaxRetentionPeriod.Milliseconds()); config.RetentionPeriodMs > max { + config.RetentionPeriodMs = max + } + return &config +} + +func createCmdErrorResp(cmd *pb.ClientCommand, err error) *pb.ServerMessage { + return &pb.ServerMessage{ + Version: ProtoVersionPb, + Payload: &pb.ServerMessage_Response{ + Response: &pb.CommandResponse{ + Id: cmd.Id, + Result: pb.CommandResponse_ERR, + Error: err.Error(), + }, + }, + } +} + +func createCmdOKResp(cmd *pb.ClientCommand) *pb.ServerMessage { + return &pb.ServerMessage{ + Version: ProtoVersionPb, + Payload: &pb.ServerMessage_Response{ + Response: &pb.CommandResponse{ + Id: cmd.Id, + Result: pb.CommandResponse_OK, + }, + }, + } +} diff --git a/options.go b/options.go index 4c638f2d5d..8954af7b35 100644 --- a/options.go +++ b/options.go @@ -10,6 +10,7 @@ import ( "time" circuit "github.com/libp2p/go-libp2p-circuit" + "github.com/libp2p/go-libp2p-core/connmgr" "github.com/libp2p/go-libp2p-core/crypto" "github.com/libp2p/go-libp2p-core/metrics" @@ -195,6 +196,34 @@ func ConnectionManager(connman connmgr.ConnManager) Option { } } +// Introspection configures the host to use the given Introspector, and the +// supplied Introspection Endpoint. +// +// Example: +// +// import ( +// "github.com/libp2p/go-libp2p/introspect" +// "github.com/libp2p/go-libp2p/introspect/ws" +// ) +// +// host, err := libp2p.New( +// libp2p.Introspection( +// introspect.NewDefaultIntrospector, +// ws.EndpointWithConfig(&ws.EndpointConfig{ListenAddrs: []string{"localhost:6061"}}), +// ), +// ) +// +func Introspection(introspectorCtor config.IntrospectorC, endpointCtor config.IntrospectionEndpointC) Option { + return func(cfg *Config) error { + if cfg.Introspector != nil { + return fmt.Errorf("cannot specify multiple introspectors") + } + cfg.Introspector = introspectorCtor + cfg.IntrospectionEndpoint = endpointCtor + return nil + } +} + // AddrsFactory configures libp2p to use the given address factory. func AddrsFactory(factory config.AddrsFactory) Option { return func(cfg *Config) error { diff --git a/p2p/host/basic/basic_host.go b/p2p/host/basic/basic_host.go index 545191bfef..faec29e234 100644 --- a/p2p/host/basic/basic_host.go +++ b/p2p/host/basic/basic_host.go @@ -13,17 +13,19 @@ import ( "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/introspection" "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/protocol" "github.com/libp2p/go-libp2p-core/record" + "github.com/libp2p/go-libp2p/p2p/protocol/identify" + "github.com/libp2p/go-libp2p/p2p/protocol/ping" + addrutil "github.com/libp2p/go-addr-util" "github.com/libp2p/go-eventbus" inat "github.com/libp2p/go-libp2p-nat" - "github.com/libp2p/go-libp2p/p2p/protocol/identify" - "github.com/libp2p/go-libp2p/p2p/protocol/ping" "github.com/libp2p/go-netroute" logging "github.com/ipfs/go-log" @@ -39,6 +41,8 @@ import ( // peer (for all addresses). const maxAddressResolution = 32 +var _ host.IntrospectableHost = (*BasicHost)(nil) + // addrChangeTickrInterval is the interval between two address change ticks. var addrChangeTickrInterval = 5 * time.Second @@ -103,6 +107,9 @@ type BasicHost struct { addrChangeChan chan struct{} + introspector introspection.Introspector + introspectionEndpoint introspection.Endpoint + lipMu sync.RWMutex localIPv4Addr ma.Multiaddr localIPv6Addr ma.Multiaddr @@ -146,6 +153,14 @@ type HostOpts struct { // UserAgent sets the user-agent for the host. Defaults to ClientVersion. UserAgent string + // Introspector is used by host subsystems to register themselves as metrics + // providers and fetch the current state. + Introspector introspection.Introspector + + // IntrospectionEndpoint is the introspect endpoint through which + // introspect data is served to clients. + IntrospectionEndpoint introspection.Endpoint + // DisableSignedPeerRecord disables the generation of Signed Peer Records on this host. DisableSignedPeerRecord bool } @@ -161,6 +176,7 @@ func NewHost(ctx context.Context, n network.Network, opts *HostOpts) (*BasicHost AddrsFactory: DefaultAddrsFactory, maResolver: madns.DefaultResolver, eventbus: eventbus.NewBus(), + introspector: opts.Introspector, addrChangeChan: make(chan struct{}, 1), ctx: hostCtx, ctxCancel: cancel, @@ -204,6 +220,7 @@ func NewHost(ctx context.Context, n network.Network, opts *HostOpts) (*BasicHost h.mux = opts.MultistreamMuxer } + // Start ID Service // we can't set this as a default above because it depends on the *BasicHost. if h.disableSignedPeerRecord { h.ids = identify.NewIDService(h, identify.UserAgent(opts.UserAgent), identify.DisableSignedPeerRecord()) @@ -253,6 +270,33 @@ func NewHost(ctx context.Context, n network.Network, opts *HostOpts) (*BasicHost return h, nil } +func (h *BasicHost) SetIntrospection(introspector introspection.Introspector, endpoint introspection.Endpoint) error { + if h.introspectionEndpoint != nil { + if err := h.introspectionEndpoint.Close(); err != nil { + return fmt.Errorf("failed to close existing introspection endpoint: %w", err) + } + } + if h.introspector != nil { + if err := h.introspector.Close(); err != nil { + return fmt.Errorf("failed to close existing introspector: %w", err) + } + } + h.introspector = introspector + h.introspectionEndpoint = endpoint + if h.introspectionEndpoint == nil { + return nil + } + return h.introspectionEndpoint.Start() +} + +func (h *BasicHost) Introspector() introspection.Introspector { + return h.introspector +} + +func (h *BasicHost) IntrospectionEndpoint() introspection.Endpoint { + return h.introspectionEndpoint +} + func (h *BasicHost) updateLocalIpAddr() { h.lipMu.Lock() defer h.lipMu.Unlock() @@ -934,6 +978,18 @@ func (h *BasicHost) Close() error { _ = h.emitters.evtLocalAddrsUpdated.Close() h.Network().Close() + if h.introspectionEndpoint != nil { + if err := h.introspectionEndpoint.Close(); err != nil { + log.Errorf("failed while shutting down introspection endpoint; err: %s", err) + } + } + + if h.introspector != nil { + if err := h.introspector.Close(); err != nil { + log.Errorf("failed while shutting down introspectir; err: %s", err) + } + } + h.refCount.Wait() }) diff --git a/p2p/host/routed/routed.go b/p2p/host/routed/routed.go index aecab09568..dc2c0fd5b9 100644 --- a/p2p/host/routed/routed.go +++ b/p2p/host/routed/routed.go @@ -8,6 +8,7 @@ import ( "github.com/libp2p/go-libp2p-core/connmgr" "github.com/libp2p/go-libp2p-core/event" "github.com/libp2p/go-libp2p-core/host" + "github.com/libp2p/go-libp2p-core/introspection" "github.com/libp2p/go-libp2p-core/network" "github.com/libp2p/go-libp2p-core/peer" "github.com/libp2p/go-libp2p-core/peerstore" @@ -22,6 +23,8 @@ import ( var log = logging.Logger("routedhost") +var _ host.IntrospectableHost = (*RoutedHost)(nil) + // AddressTTL is the expiry time for our addresses. // We expire them quickly. const AddressTTL = time.Second * 10 @@ -134,6 +137,22 @@ func logRoutingErrDifferentPeers(ctx context.Context, wanted, got peer.ID, err e log.Event(ctx, "routingError", lm) } +func (h *RoutedHost) Introspector() introspection.Introspector { + i, ok := h.host.(host.IntrospectableHost) + if !ok { + return nil + } + return i.Introspector() +} + +func (h *RoutedHost) IntrospectionEndpoint() introspection.Endpoint { + i, ok := h.host.(host.IntrospectableHost) + if !ok { + return nil + } + return i.IntrospectionEndpoint() +} + func (rh *RoutedHost) ID() peer.ID { return rh.host.ID() } diff --git a/p2p/net/mock/mock_conn.go b/p2p/net/mock/mock_conn.go index 6bde1ea2dc..d2780e410c 100644 --- a/p2p/net/mock/mock_conn.go +++ b/p2p/net/mock/mock_conn.go @@ -2,7 +2,9 @@ package mocknet import ( "container/list" + "strconv" "sync" + "sync/atomic" process "github.com/jbenet/goprocess" ic "github.com/libp2p/go-libp2p-core/crypto" @@ -12,12 +14,16 @@ import ( manet "github.com/multiformats/go-multiaddr-net" ) +var connCounter int64 + // conn represents one side's perspective of a // live connection between two peers. // it goes over a particular link. type conn struct { notifLk sync.Mutex + id int64 + local peer.ID remote peer.ID @@ -43,6 +49,7 @@ func newConn(p process.Process, ln, rn *peernet, l *link, dir network.Direction) c.local = ln.peer c.remote = rn.peer c.stat = network.Stat{Direction: dir} + c.id = atomic.AddInt64(&connCounter, 1) c.localAddr = ln.ps.Addrs(ln.peer)[0] for _, a := range rn.ps.Addrs(rn.peer) { @@ -61,6 +68,10 @@ func newConn(p process.Process, ln, rn *peernet, l *link, dir network.Direction) return c } +func (c *conn) ID() string { + return strconv.FormatInt(c.id, 10) +} + func (c *conn) Close() error { return c.pairProc.Close() } diff --git a/p2p/net/mock/mock_stream.go b/p2p/net/mock/mock_stream.go index ecb32ddbbf..9ddf5eedf2 100644 --- a/p2p/net/mock/mock_stream.go +++ b/p2p/net/mock/mock_stream.go @@ -5,6 +5,7 @@ import ( "errors" "io" "net" + "strconv" "sync" "sync/atomic" "time" @@ -14,12 +15,15 @@ import ( protocol "github.com/libp2p/go-libp2p-core/protocol" ) +var streamCounter int64 + // stream implements network.Stream type stream struct { notifLk sync.Mutex rstream *stream conn *conn + id int64 write *io.PipeWriter read *io.PipeReader @@ -57,6 +61,7 @@ func newStream(w *io.PipeWriter, r *io.PipeReader, dir network.Direction) *strea s := &stream{ read: r, write: w, + id: atomic.AddInt64(&streamCounter, 1), reset: make(chan struct{}, 1), close: make(chan struct{}, 1), closed: make(chan struct{}), @@ -86,6 +91,10 @@ func (s *stream) Write(p []byte) (n int, err error) { return len(p), nil } +func (s *stream) ID() string { + return strconv.FormatInt(s.id, 10) +} + func (s *stream) Protocol() protocol.ID { // Ignore type error. It means that the protocol is unset. p, _ := s.protocol.Load().(protocol.ID)