feat: add identify option to swarm peers command
Fixes #9578
Arthur Gavazza committed
Mar 30, 2023 at 01:34 UTC
e89cce63fd1428c40031edb30b407cbb468abb08
4 files changed
+210
-50
core/commands/swarm.go
+64
-7
@@ -2,6 +2,7 @@ package commands
2
3
import (
4
"context"
5
+ "encoding/base64"
6
"encoding/json"
7
"errors"
8
"fmt"
@@ -21,8 +22,10 @@ import (
22
"github.com/ipfs/kubo/repo/fsrepo"
23
24
cmds "github.com/ipfs/go-ipfs-cmds"
25
+ ic "github.com/libp2p/go-libp2p/core/crypto"
26
inet "github.com/libp2p/go-libp2p/core/network"
27
"github.com/libp2p/go-libp2p/core/peer"
28
+ pstore "github.com/libp2p/go-libp2p/core/peerstore"
29
rcmgr "github.com/libp2p/go-libp2p/p2p/host/resource-manager"
30
ma "github.com/multiformats/go-multiaddr"
31
madns "github.com/multiformats/go-multiaddr-dns"
@@ -69,6 +72,7 @@ const (
72
swarmDirectionOptionName = "direction"
73
swarmResetLimitsOptionName = "reset"
74
swarmUsedResourcesPercentageName = "min-used-limit-perc"
75
+ swarmIdentifyOptionName = "identify"
76
)
77
78
type peeringResult struct {
@@ -236,17 +240,18 @@ var swarmPeersCmd = &cmds.Command{
240
cmds.BoolOption(swarmStreamsOptionName, "Also list information about open streams for each peer"),
241
cmds.BoolOption(swarmLatencyOptionName, "Also list information about latency to each peer"),
242
cmds.BoolOption(swarmDirectionOptionName, "Also list information about the direction of connection"),
243
+ cmds.BoolOption(swarmIdentifyOptionName, "Also list information about peers identify"),
244
},
245
Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
246
api, err := cmdenv.GetApi(env, req)
247
if err != nil {
248
return err
249
}
245
-
250
verbose, _ := req.Options[swarmVerboseOptionName].(bool)
251
latency, _ := req.Options[swarmLatencyOptionName].(bool)
252
streams, _ := req.Options[swarmStreamsOptionName].(bool)
253
direction, _ := req.Options[swarmDirectionOptionName].(bool)
254
+ identify, _ := req.Options[swarmIdentifyOptionName].(bool)
255
256
conns, err := api.Swarm().Peers(req.Context)
257
if err != nil {
@@ -287,6 +292,15 @@ var swarmPeersCmd = &cmds.Command{
292
ci.Streams = append(ci.Streams, streamInfo{Protocol: string(s)})
293
}
294
}
295
+
296
+ if verbose || identify {
297
+ n, err := cmdenv.GetNode(env)
298
+ if err != nil {
299
+ return err
300
+ }
301
+ identifyResult, _ := ci.identifyPeer(n.Peerstore, c.ID())
302
+ ci.Identify = identifyResult
303
+ }
304
sort.Sort(&ci)
305
out.Peers = append(out.Peers, ci)
306
}
@@ -411,12 +425,13 @@ type streamInfo struct {
425
}
426
427
type connInfo struct {
414
- Addr string
415
- Peer string
416
- Latency string
417
- Muxer string
418
- Direction inet.Direction
419
- Streams []streamInfo
428
+ Addr string `json:",omitempty"`
429
+ Peer string `json:",omitempty"`
430
+ Latency string `json:",omitempty"`
431
+ Muxer string `json:",omitempty"`
432
+ Direction inet.Direction `json:",omitempty"`
433
+ Streams []streamInfo `json:",omitempty"`
434
+ Identify IdOutput `json:",omitempty"`
435
}
436
437
func (ci *connInfo) Less(i, j int) bool {
@@ -447,6 +462,48 @@ func (ci connInfos) Swap(i, j int) {
462
ci.Peers[i], ci.Peers[j] = ci.Peers[j], ci.Peers[i]
463
}
464
465
+func (ci *connInfo) identifyPeer(ps pstore.Peerstore, p peer.ID) (IdOutput, error) {
466
+ var info IdOutput
467
+ info.ID = p.String()
468
+
469
+ if pk := ps.PubKey(p); pk != nil {
470
+ pkb, err := ic.MarshalPublicKey(pk)
471
+ if err != nil {
472
+ return IdOutput{}, err
473
+ }
474
+ info.PublicKey = base64.StdEncoding.EncodeToString(pkb)
475
+ }
476
+
477
+ addrInfo := ps.PeerInfo(p)
478
+ addrs, err := peer.AddrInfoToP2pAddrs(&addrInfo)
479
+ if err != nil {
480
+ return IdOutput{}, err
481
+ }
482
+
483
+ for _, a := range addrs {
484
+ info.Addresses = append(info.Addresses, a.String())
485
+ }
486
+ sort.Strings(info.Addresses)
487
+
488
+ if protocols, err := ps.GetProtocols(p); err == nil {
489
+ info.Protocols = append(info.Protocols, protocols...)
490
+ sort.Slice(info.Protocols, func(i, j int) bool { return info.Protocols[i] < info.Protocols[j] })
491
+ }
492
+
493
+ if v, err := ps.Get(p, "ProtocolVersion"); err == nil {
494
+ if vs, ok := v.(string); ok {
495
+ info.ProtocolVersion = vs
496
+ }
497
+ }
498
+ if v, err := ps.Get(p, "AgentVersion"); err == nil {
499
+ if vs, ok := v.(string); ok {
500
+ info.AgentVersion = vs
501
+ }
502
+ }
503
+
504
+ return info, nil
505
+}
506
+
507
// directionString transfers to string
508
func directionString(d inet.Direction) string {
509
switch d {
test/cli/harness/peering.go
new
+37
@@ -0,0 +1,37 @@
1
+package harness
2
+
3
+import (
4
+ "fmt"
5
+ "math/rand"
6
+ "testing"
7
+
8
+ "github.com/ipfs/kubo/config"
9
+)
10
+
11
+type Peering struct {
12
+ From int
13
+ To int
14
+}
15
+
16
+func newRandPort() int {
17
+ n := rand.Int()
18
+ return 3000 + (n % 1000)
19
+}
20
+
21
+func CreatePeerNodes(t *testing.T, n int, peerings []Peering) (*Harness, Nodes) {
22
+ h := NewT(t)
23
+ nodes := h.NewNodes(n).Init()
24
+ nodes.ForEachPar(func(node *Node) {
25
+ node.UpdateConfig(func(cfg *config.Config) {
26
+ cfg.Routing.Type = config.NewOptionalString("none")
27
+ cfg.Addresses.Swarm = []string{fmt.Sprintf("/ip4/127.0.0.1/tcp/%d", newRandPort())}
28
+ })
29
+
30
+ })
31
+
32
+ for _, peering := range peerings {
33
+ nodes[peering.From].PeerWith(nodes[peering.To])
34
+ }
35
+
36
+ return h, nodes
37
+}
test/cli/peering_test.go
+12
-43
@@ -1,12 +1,9 @@
1
package cli
2
3
import (
4
- "fmt"
5
- "math/rand"
4
"testing"
5
"time"
6
9
- "github.com/ipfs/kubo/config"
7
"github.com/ipfs/kubo/test/cli/harness"
8
. "github.com/ipfs/kubo/test/cli/testutils"
9
"github.com/libp2p/go-libp2p/core/peer"
@@ -16,16 +13,6 @@ import (
13
func TestPeering(t *testing.T) {
14
t.Parallel()
15
19
- type peering struct {
20
- from int
21
- to int
22
- }
23
-
24
- newRandPort := func() int {
25
- n := rand.Int()
26
- return 3000 + (n % 1000)
27
- }
28
-
16
containsPeerID := func(p peer.ID, peers []peer.ID) bool {
17
for _, peerID := range peers {
18
if p == peerID {
@@ -63,34 +50,16 @@ func TestPeering(t *testing.T) {
50
}, 20*time.Second, 10*time.Millisecond, "%d -> %d peered", from.ID, to.ID)
51
}
52
66
- assertPeerings := func(h *harness.Harness, nodes []*harness.Node, peerings []peering) {
67
- ForEachPar(peerings, func(peering peering) {
68
- assertPeered(h, nodes[peering.from], nodes[peering.to])
53
+ assertPeerings := func(h *harness.Harness, nodes []*harness.Node, peerings []harness.Peering) {
54
+ ForEachPar(peerings, func(peering harness.Peering) {
55
+ assertPeered(h, nodes[peering.From], nodes[peering.To])
56
})
57
}
58
72
- createNodes := func(t *testing.T, n int, peerings []peering) (*harness.Harness, harness.Nodes) {
73
- h := harness.NewT(t)
74
- nodes := h.NewNodes(n).Init()
75
- nodes.ForEachPar(func(node *harness.Node) {
76
- node.UpdateConfig(func(cfg *config.Config) {
77
- cfg.Routing.Type = config.NewOptionalString("none")
78
- cfg.Addresses.Swarm = []string{fmt.Sprintf("/ip4/127.0.0.1/tcp/%d", newRandPort())}
79
- })
80
-
81
- })
82
-
83
- for _, peering := range peerings {
84
- nodes[peering.from].PeerWith(nodes[peering.to])
85
- }
86
-
87
- return h, nodes
88
- }
89
-
59
t.Run("bidirectional peering should work (simultaneous connect)", func(t *testing.T) {
60
t.Parallel()
92
- peerings := []peering{{from: 0, to: 1}, {from: 1, to: 0}, {from: 1, to: 2}}
93
- h, nodes := createNodes(t, 3, peerings)
61
+ peerings := []harness.Peering{{From: 0, To: 1}, {From: 1, To: 0}, {From: 1, To: 2}}
62
+ h, nodes := harness.CreatePeerNodes(t, 3, peerings)
63
64
nodes.StartDaemons()
65
assertPeerings(h, nodes, peerings)
@@ -101,8 +70,8 @@ func TestPeering(t *testing.T) {
70
71
t.Run("1 should reconnect to 2 when 2 disconnects from 1", func(t *testing.T) {
72
t.Parallel()
104
- peerings := []peering{{from: 0, to: 1}, {from: 1, to: 0}, {from: 1, to: 2}}
105
- h, nodes := createNodes(t, 3, peerings)
73
+ peerings := []harness.Peering{{From: 0, To: 1}, {From: 1, To: 0}, {From: 1, To: 2}}
74
+ h, nodes := harness.CreatePeerNodes(t, 3, peerings)
75
76
nodes.StartDaemons()
77
assertPeerings(h, nodes, peerings)
@@ -113,12 +82,12 @@ func TestPeering(t *testing.T) {
82
83
t.Run("1 will peer with 2 when it comes online", func(t *testing.T) {
84
t.Parallel()
116
- peerings := []peering{{from: 0, to: 1}, {from: 1, to: 0}, {from: 1, to: 2}}
117
- h, nodes := createNodes(t, 3, peerings)
85
+ peerings := []harness.Peering{{From: 0, To: 1}, {From: 1, To: 0}, {From: 1, To: 2}}
86
+ h, nodes := harness.CreatePeerNodes(t, 3, peerings)
87
88
nodes[0].StartDaemon()
89
nodes[1].StartDaemon()
121
- assertPeerings(h, nodes, []peering{{from: 0, to: 1}, {from: 1, to: 0}})
90
+ assertPeerings(h, nodes, []harness.Peering{{From: 0, To: 1}, {From: 1, To: 0}})
91
92
nodes[2].StartDaemon()
93
assertPeerings(h, nodes, peerings)
@@ -126,8 +95,8 @@ func TestPeering(t *testing.T) {
95
96
t.Run("1 will re-peer with 2 when it disconnects and then comes back online", func(t *testing.T) {
97
t.Parallel()
129
- peerings := []peering{{from: 0, to: 1}, {from: 1, to: 0}, {from: 1, to: 2}}
130
- h, nodes := createNodes(t, 3, peerings)
98
+ peerings := []harness.Peering{{From: 0, To: 1}, {From: 1, To: 0}, {From: 1, To: 2}}
99
+ h, nodes := harness.CreatePeerNodes(t, 3, peerings)
100
101
nodes.StartDaemons()
102
assertPeerings(h, nodes, peerings)
test/cli/swarm_test.go
new
+97
@@ -0,0 +1,97 @@
1
+package cli
2
+
3
+import (
4
+ "encoding/json"
5
+ "fmt"
6
+ "testing"
7
+
8
+ "github.com/ipfs/kubo/test/cli/harness"
9
+
10
+ "github.com/stretchr/testify/assert"
11
+)
12
+
13
+// TODO: Migrate the rest of the sharness swarm test.
14
+func TestSwarm(t *testing.T) {
15
+ type identifyType struct {
16
+ ID string
17
+ PublicKey string
18
+ Addresses []string
19
+ AgentVersion string
20
+ ProtocolVersion string
21
+ Protocols []string
22
+ }
23
+ type peer struct {
24
+ Identify identifyType
25
+ }
26
+ type expectedOutputType struct {
27
+ Peers []peer
28
+ }
29
+
30
+ t.Parallel()
31
+
32
+ t.Run("ipfs swarm peers returns empty peers when a node is not connected to any peers", func(t *testing.T) {
33
+ t.Parallel()
34
+ node := harness.NewT(t).NewNode().Init().StartDaemon()
35
+ res := node.RunIPFS("swarm", "peers", "--enc=json", "--identify")
36
+ var output expectedOutputType
37
+ err := json.Unmarshal(res.Stdout.Bytes(), &output)
38
+ assert.Nil(t, err)
39
+ assert.Equal(t, 0, len(output.Peers))
40
+
41
+ })
42
+ t.Run("ipfs swarm peers with flag identify outputs expected identify information about connected peers", func(t *testing.T) {
43
+ t.Parallel()
44
+ node := harness.NewT(t).NewNode().Init().StartDaemon()
45
+ otherNode := harness.NewT(t).NewNode().Init().StartDaemon()
46
+ node.Connect(otherNode)
47
+
48
+ res := node.RunIPFS("swarm", "peers", "--enc=json", "--identify")
49
+ var output expectedOutputType
50
+ err := json.Unmarshal(res.Stdout.Bytes(), &output)
51
+ assert.Nil(t, err)
52
+ actualID := output.Peers[0].Identify.ID
53
+ actualPublicKey := output.Peers[0].Identify.PublicKey
54
+ actualAgentVersion := output.Peers[0].Identify.AgentVersion
55
+ actualAdresses := output.Peers[0].Identify.Addresses
56
+ actualProtocolVersion := output.Peers[0].Identify.ProtocolVersion
57
+ actualProtocols := output.Peers[0].Identify.Protocols
58
+
59
+ expectedID := otherNode.PeerID().String()
60
+ expectedAddresses := []string{fmt.Sprintf("%s/p2p/%s", otherNode.SwarmAddrs()[0], actualID)}
61
+
62
+ assert.Equal(t, actualID, expectedID)
63
+ assert.NotNil(t, actualPublicKey)
64
+ assert.NotNil(t, actualAgentVersion)
65
+ assert.NotNil(t, actualProtocolVersion)
66
+ assert.Len(t, actualAdresses, 1)
67
+ assert.Equal(t, expectedAddresses[0], actualAdresses[0])
68
+ assert.Greater(t, len(actualProtocols), 0)
69
+
70
+ })
71
+
72
+ t.Run("ipfs swarm peers with flag identify outputs Identify field with data that matches calling ipfs id on a peer", func(t *testing.T) {
73
+ t.Parallel()
74
+ node := harness.NewT(t).NewNode().Init().StartDaemon()
75
+ otherNode := harness.NewT(t).NewNode().Init().StartDaemon()
76
+ node.Connect(otherNode)
77
+
78
+ otherNodeIDResponse := otherNode.RunIPFS("id", "--enc=json")
79
+ var otherNodeIDOutput identifyType
80
+ err := json.Unmarshal(otherNodeIDResponse.Stdout.Bytes(), &otherNodeIDOutput)
81
+ assert.Nil(t, err)
82
+ res := node.RunIPFS("swarm", "peers", "--enc=json", "--identify")
83
+
84
+ var output expectedOutputType
85
+ err = json.Unmarshal(res.Stdout.Bytes(), &output)
86
+ assert.Nil(t, err)
87
+ outputIdentify := output.Peers[0].Identify
88
+
89
+ assert.Equal(t, outputIdentify.ID, otherNodeIDOutput.ID)
90
+ assert.Equal(t, outputIdentify.PublicKey, otherNodeIDOutput.PublicKey)
91
+ assert.Equal(t, outputIdentify.AgentVersion, otherNodeIDOutput.AgentVersion)
92
+ assert.Equal(t, outputIdentify.ProtocolVersion, otherNodeIDOutput.ProtocolVersion)
93
+ assert.ElementsMatch(t, outputIdentify.Addresses, otherNodeIDOutput.Addresses)
94
+ assert.ElementsMatch(t, outputIdentify.Protocols, otherNodeIDOutput.Protocols)
95
+
96
+ })
97
+}