master
go 1,077 lines 26.4 KB
Raw
1 package commands
2
3 import (
4 "context"
5 "encoding/base64"
6 "encoding/json"
7 "errors"
8 "fmt"
9 "io"
10 "path"
11 "slices"
12 "strconv"
13 "strings"
14 "sync"
15 "text/tabwriter"
16 "time"
17
18 "github.com/ipfs/kubo/commands"
19 "github.com/ipfs/kubo/config"
20 "github.com/ipfs/kubo/core/commands/cmdenv"
21 "github.com/ipfs/kubo/core/commands/cmdutils"
22 "github.com/ipfs/kubo/core/node/libp2p"
23 "github.com/ipfs/kubo/repo"
24 "github.com/ipfs/kubo/repo/fsrepo"
25
26 cmds "github.com/ipfs/go-ipfs-cmds"
27 ic "github.com/libp2p/go-libp2p/core/crypto"
28 inet "github.com/libp2p/go-libp2p/core/network"
29 "github.com/libp2p/go-libp2p/core/peer"
30 pstore "github.com/libp2p/go-libp2p/core/peerstore"
31 "github.com/libp2p/go-libp2p/core/protocol"
32 rcmgr "github.com/libp2p/go-libp2p/p2p/host/resource-manager"
33 ma "github.com/multiformats/go-multiaddr"
34 madns "github.com/multiformats/go-multiaddr-dns"
35 mamask "github.com/whyrusleeping/multiaddr-filter"
36 )
37
38 const (
39 dnsResolveTimeout = 10 * time.Second
40 )
41
42 type stringList struct {
43 Strings []string
44 }
45
46 type addrMap struct {
47 Addrs map[string][]string
48 }
49
50 var SwarmCmd = &cmds.Command{
51 Helptext: cmds.HelpText{
52 Tagline: "Interact with the swarm.",
53 ShortDescription: `
54 'ipfs swarm' is a tool to manipulate the network swarm. The swarm is the
55 component that opens, listens for, and maintains connections to other
56 ipfs peers in the internet.
57 `,
58 },
59 Subcommands: map[string]*cmds.Command{
60 "addrs": swarmAddrsCmd,
61 "connect": swarmConnectCmd,
62 "disconnect": swarmDisconnectCmd,
63 "filters": swarmFiltersCmd,
64 "peers": swarmPeersCmd,
65 "peering": swarmPeeringCmd,
66 "resources": swarmResourcesCmd, // libp2p Network Resource Manager
67
68 },
69 }
70
71 const (
72 swarmVerboseOptionName = "verbose"
73 swarmStreamsOptionName = "streams"
74 swarmLatencyOptionName = "latency"
75 swarmDirectionOptionName = "direction"
76 swarmResetLimitsOptionName = "reset"
77 swarmUsedResourcesPercentageName = "min-used-limit-perc"
78 swarmIdentifyOptionName = "identify"
79 )
80
81 type peeringResult struct {
82 ID peer.ID
83 Status string
84 }
85
86 var swarmPeeringCmd = &cmds.Command{
87 Helptext: cmds.HelpText{
88 Tagline: "Modify the peering subsystem.",
89 ShortDescription: `
90 'ipfs swarm peering' manages the peering subsystem.
91 Peers in the peering subsystem are maintained to be connected, reconnected
92 on disconnect with a back-off.
93 The changes are not saved to the config.
94 `,
95 },
96 Subcommands: map[string]*cmds.Command{
97 "add": swarmPeeringAddCmd,
98 "ls": swarmPeeringLsCmd,
99 "rm": swarmPeeringRmCmd,
100 },
101 }
102
103 var swarmPeeringAddCmd = &cmds.Command{
104 Helptext: cmds.HelpText{
105 Tagline: "Add peers into the peering subsystem.",
106 ShortDescription: `
107 'ipfs swarm peering add' will add the new address to the peering subsystem as one that should always be connected to.
108 `,
109 },
110 Arguments: []cmds.Argument{
111 cmds.StringArg("address", true, true, "address of peer to add into the peering subsystem"),
112 },
113 Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
114 addrs := make([]ma.Multiaddr, len(req.Arguments))
115
116 for i, arg := range req.Arguments {
117 addr, err := ma.NewMultiaddr(arg)
118 if err != nil {
119 return err
120 }
121
122 addrs[i] = addr
123 }
124
125 addInfos, err := peer.AddrInfosFromP2pAddrs(addrs...)
126 if err != nil {
127 return err
128 }
129
130 node, err := cmdenv.GetNode(env)
131 if err != nil {
132 return err
133 }
134 if !node.IsOnline {
135 return ErrNotOnline
136 }
137
138 for _, addrinfo := range addInfos {
139 node.Peering.AddPeer(addrinfo)
140 err = res.Emit(peeringResult{addrinfo.ID, "success"})
141 if err != nil {
142 return err
143 }
144 }
145 return nil
146 },
147 Encoders: cmds.EncoderMap{
148 cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, pr *peeringResult) error {
149 fmt.Fprintf(w, "add %s %s\n", pr.ID.String(), pr.Status)
150 return nil
151 }),
152 },
153 Type: peeringResult{},
154 }
155
156 var swarmPeeringLsCmd = &cmds.Command{
157 Helptext: cmds.HelpText{
158 Tagline: "List peers registered in the peering subsystem.",
159 ShortDescription: `
160 'ipfs swarm peering ls' lists the peers that are registered in the peering subsystem and to which the daemon is always connected.
161 `,
162 },
163 Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
164 node, err := cmdenv.GetNode(env)
165 if err != nil {
166 return err
167 }
168 if !node.IsOnline {
169 return ErrNotOnline
170 }
171
172 peers := node.Peering.ListPeers()
173 return cmds.EmitOnce(res, addrInfos{Peers: peers})
174 },
175 Type: addrInfos{},
176 Encoders: cmds.EncoderMap{
177 cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, ai *addrInfos) error {
178 for _, info := range ai.Peers {
179 fmt.Fprintf(w, "%s\n", info.ID)
180 for _, addr := range info.Addrs {
181 fmt.Fprintf(w, "\t%s\n", addr)
182 }
183 }
184 return nil
185 }),
186 },
187 }
188
189 type addrInfos struct {
190 Peers []peer.AddrInfo
191 }
192
193 var swarmPeeringRmCmd = &cmds.Command{
194 Helptext: cmds.HelpText{
195 Tagline: "Remove a peer from the peering subsystem.",
196 ShortDescription: `
197 'ipfs swarm peering rm' will remove the given ID from the peering subsystem and remove it from the always-on connection.
198 `,
199 },
200 Arguments: []cmds.Argument{
201 cmds.StringArg("ID", true, true, "ID of peer to remove from the peering subsystem"),
202 },
203 Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
204 node, err := cmdenv.GetNode(env)
205 if err != nil {
206 return err
207 }
208 if !node.IsOnline {
209 return ErrNotOnline
210 }
211
212 for _, arg := range req.Arguments {
213 id, err := peer.Decode(arg)
214 if err != nil {
215 return err
216 }
217
218 node.Peering.RemovePeer(id)
219 if err = res.Emit(peeringResult{id, "success"}); err != nil {
220 return err
221 }
222 }
223 return nil
224 },
225 Type: peeringResult{},
226 Encoders: cmds.EncoderMap{
227 cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, pr *peeringResult) error {
228 fmt.Fprintf(w, "remove %s %s\n", pr.ID.String(), pr.Status)
229 return nil
230 }),
231 },
232 }
233
234 var swarmPeersCmd = &cmds.Command{
235 Helptext: cmds.HelpText{
236 Tagline: "List peers with open connections.",
237 ShortDescription: `
238 'ipfs swarm peers' lists the set of peers this node is connected to.
239 `,
240 },
241 Options: []cmds.Option{
242 cmds.BoolOption(swarmVerboseOptionName, "v", "display all extra information"),
243 cmds.BoolOption(swarmStreamsOptionName, "Also list information about open streams for each peer"),
244 cmds.BoolOption(swarmLatencyOptionName, "Also list information about latency to each peer"),
245 cmds.BoolOption(swarmDirectionOptionName, "Also list information about the direction of connection"),
246 cmds.BoolOption(swarmIdentifyOptionName, "Also list information about peers identify"),
247 },
248 Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
249 api, err := cmdenv.GetApi(env, req)
250 if err != nil {
251 return err
252 }
253 verbose, _ := req.Options[swarmVerboseOptionName].(bool)
254 latency, _ := req.Options[swarmLatencyOptionName].(bool)
255 streams, _ := req.Options[swarmStreamsOptionName].(bool)
256 direction, _ := req.Options[swarmDirectionOptionName].(bool)
257 identify, _ := req.Options[swarmIdentifyOptionName].(bool)
258
259 conns, err := api.Swarm().Peers(req.Context)
260 if err != nil {
261 return err
262 }
263
264 var out connInfos
265 for _, c := range conns {
266 ci := connInfo{
267 Addr: c.Address().String(),
268 Peer: c.ID().String(),
269 }
270
271 if verbose || direction {
272 // set direction
273 ci.Direction = c.Direction()
274 }
275
276 if verbose || latency {
277 lat, err := c.Latency()
278 if err != nil {
279 return err
280 }
281
282 if lat == 0 {
283 ci.Latency = "n/a"
284 } else {
285 ci.Latency = lat.String()
286 }
287 }
288 if verbose || streams {
289 strs, err := c.Streams()
290 if err != nil {
291 return err
292 }
293
294 for _, s := range strs {
295 ci.Streams = append(ci.Streams, streamInfo{Protocol: cmdutils.CleanAndTrim(string(s))})
296 }
297 }
298
299 if verbose || identify {
300 n, err := cmdenv.GetNode(env)
301 if err != nil {
302 return err
303 }
304 identifyResult, _ := ci.identifyPeer(n.Peerstore, c.ID())
305 ci.Identify = identifyResult
306 }
307 ci.Sort()
308 out.Peers = append(out.Peers, ci)
309 }
310
311 out.Sort()
312 return cmds.EmitOnce(res, &out)
313 },
314 Encoders: cmds.EncoderMap{
315 cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, ci *connInfos) error {
316 pipfs := ma.ProtocolWithCode(ma.P_IPFS).Name
317 for _, info := range ci.Peers {
318 fmt.Fprintf(w, "%s/%s/%s", info.Addr, pipfs, info.Peer)
319 if info.Latency != "" {
320 fmt.Fprintf(w, " %s", info.Latency)
321 }
322
323 if info.Direction != inet.DirUnknown {
324 fmt.Fprintf(w, " %s", directionString(info.Direction))
325 }
326 fmt.Fprintln(w)
327
328 for _, s := range info.Streams {
329 if s.Protocol == "" {
330 s.Protocol = "<no protocol name>"
331 }
332
333 fmt.Fprintf(w, " %s\n", s.Protocol)
334 }
335 }
336
337 return nil
338 }),
339 },
340 Type: connInfos{},
341 }
342
343 var swarmResourcesCmd = &cmds.Command{
344 Status: cmds.Experimental,
345 Helptext: cmds.HelpText{
346 Tagline: "Get a summary of all resources accounted for by the libp2p Resource Manager.",
347 LongDescription: `
348 Get a summary of all resources accounted for by the libp2p Resource Manager.
349 This includes the limits and the usage against those limits.
350 This can output a human readable table and JSON encoding.
351 `,
352 },
353 Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
354 node, err := cmdenv.GetNode(env)
355 if err != nil {
356 return err
357 }
358
359 if node.ResourceManager == nil {
360 return libp2p.ErrNoResourceMgr
361 }
362
363 cfg, err := node.Repo.Config()
364 if err != nil {
365 return err
366 }
367
368 userResourceOverrides, err := node.Repo.UserResourceOverrides()
369 if err != nil {
370 return err
371 }
372
373 // FIXME: we shouldn't recompute limits, either save them or load them from libp2p (https://github.com/libp2p/go-libp2p/issues/2166)
374 limitConfig, _, err := libp2p.LimitConfig(cfg.Swarm, userResourceOverrides)
375 if err != nil {
376 return err
377 }
378
379 rapi, ok := node.ResourceManager.(rcmgr.ResourceManagerState)
380 if !ok { // NullResourceManager
381 return libp2p.ErrNoResourceMgr
382 }
383
384 return cmds.EmitOnce(res, libp2p.MergeLimitsAndStatsIntoLimitsConfigAndUsage(limitConfig, rapi.Stat()))
385 },
386 Encoders: cmds.EncoderMap{
387 cmds.JSON: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, limitsAndUsage libp2p.LimitsConfigAndUsage) error {
388 return json.NewEncoder(w).Encode(limitsAndUsage)
389 }),
390 cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, limitsAndUsage libp2p.LimitsConfigAndUsage) error {
391 tw := tabwriter.NewWriter(w, 20, 8, 0, '\t', 0)
392 defer tw.Flush()
393
394 fmt.Fprintf(tw, "%s\t%s\t%s\t%s\t%s\t\n", "Scope", "Limit Name", "Limit Value", "Limit Usage Amount", "Limit Usage Percent")
395 for _, ri := range libp2p.LimitConfigsToInfo(limitsAndUsage) {
396 var limit, percentage string
397 switch ri.LimitValue {
398 case rcmgr.Unlimited64:
399 limit = "unlimited"
400 percentage = "n/a"
401 case rcmgr.BlockAllLimit64:
402 limit = "blockAll"
403 percentage = "n/a"
404 default:
405 limit = strconv.FormatInt(int64(ri.LimitValue), 10)
406 if ri.CurrentUsage == 0 {
407 percentage = "0%"
408 } else {
409 percentage = strconv.FormatFloat(float64(ri.CurrentUsage)/float64(ri.LimitValue)*100, 'f', 1, 64) + "%"
410 }
411 }
412 fmt.Fprintf(tw, "%s\t%s\t%s\t%d\t%s\t\n",
413 ri.ScopeName,
414 ri.LimitName,
415 limit,
416 ri.CurrentUsage,
417 percentage,
418 )
419 }
420
421 return nil
422 }),
423 },
424 Type: libp2p.LimitsConfigAndUsage{},
425 }
426
427 type streamInfo struct {
428 Protocol string
429 }
430
431 type connInfo struct {
432 Addr string `json:",omitempty"`
433 Peer string `json:",omitempty"`
434 Latency string `json:",omitempty"`
435 Muxer string `json:",omitempty"`
436 Direction inet.Direction `json:",omitempty"`
437 Streams []streamInfo `json:",omitempty"`
438 Identify IdOutput
439 }
440
441 func (ci *connInfo) Sort() {
442 slices.SortFunc(ci.Streams, func(a, b streamInfo) int {
443 return strings.Compare(a.Protocol, b.Protocol)
444 })
445 }
446
447 type connInfos struct {
448 Peers []connInfo
449 }
450
451 func (ci *connInfos) Sort() {
452 slices.SortFunc(ci.Peers, func(a, b connInfo) int {
453 return strings.Compare(a.Addr, b.Addr)
454 })
455 }
456
457 func (ci *connInfo) identifyPeer(ps pstore.Peerstore, p peer.ID) (IdOutput, error) {
458 var info IdOutput
459 info.ID = p.String()
460
461 if pk := ps.PubKey(p); pk != nil {
462 pkb, err := ic.MarshalPublicKey(pk)
463 if err != nil {
464 return IdOutput{}, err
465 }
466 info.PublicKey = base64.StdEncoding.EncodeToString(pkb)
467 }
468
469 addrInfo := ps.PeerInfo(p)
470 addrs, err := peer.AddrInfoToP2pAddrs(&addrInfo)
471 if err != nil {
472 return IdOutput{}, err
473 }
474
475 for _, a := range addrs {
476 info.Addresses = append(info.Addresses, a.String())
477 }
478 slices.Sort(info.Addresses)
479
480 if protocols, err := ps.GetProtocols(p); err == nil {
481 for _, proto := range protocols {
482 info.Protocols = append(info.Protocols, protocol.ID(cmdutils.CleanAndTrim(string(proto))))
483 }
484 slices.Sort(info.Protocols)
485 }
486
487 if v, err := ps.Get(p, "AgentVersion"); err == nil {
488 if vs, ok := v.(string); ok {
489 info.AgentVersion = cmdutils.CleanAndTrim(vs)
490 }
491 }
492
493 return info, nil
494 }
495
496 // directionString transfers to string
497 func directionString(d inet.Direction) string {
498 switch d {
499 case inet.DirInbound:
500 return "inbound"
501 case inet.DirOutbound:
502 return "outbound"
503 default:
504 return ""
505 }
506 }
507
508 var swarmAddrsCmd = &cmds.Command{
509 Helptext: cmds.HelpText{
510 Tagline: "List known addresses. Useful for debugging.",
511 ShortDescription: `
512 'ipfs swarm addrs' lists all addresses this node is aware of.
513 `,
514 },
515 Subcommands: map[string]*cmds.Command{
516 "autonat": swarmAddrsAutoNATCmd,
517 "local": swarmAddrsLocalCmd,
518 "listen": swarmAddrsListenCmd,
519 },
520 Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
521 api, err := cmdenv.GetApi(env, req)
522 if err != nil {
523 return err
524 }
525
526 addrs, err := api.Swarm().KnownAddrs(req.Context)
527 if err != nil {
528 return err
529 }
530
531 out := make(map[string][]string)
532 for p, paddrs := range addrs {
533 s := p.String()
534 for _, a := range paddrs {
535 out[s] = append(out[s], a.String())
536 }
537 }
538
539 return cmds.EmitOnce(res, &addrMap{Addrs: out})
540 },
541 Encoders: cmds.EncoderMap{
542 cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, am *addrMap) error {
543 // sort the ids first
544 ids := make([]string, 0, len(am.Addrs))
545 for p := range am.Addrs {
546 ids = append(ids, p)
547 }
548 slices.Sort(ids)
549
550 for _, p := range ids {
551 paddrs := am.Addrs[p]
552 fmt.Fprintf(w, "%s (%d)\n", p, len(paddrs))
553 for _, addr := range paddrs {
554 fmt.Fprintf(w, "\t%s\n", addr)
555 }
556 }
557
558 return nil
559 }),
560 },
561 Type: addrMap{},
562 }
563
564 var swarmAddrsLocalCmd = &cmds.Command{
565 Helptext: cmds.HelpText{
566 Tagline: "List local addresses.",
567 ShortDescription: `
568 'ipfs swarm addrs local' lists all local listening addresses announced to the network.
569 `,
570 },
571 Options: []cmds.Option{
572 cmds.BoolOption("id", "Show peer ID in addresses."),
573 },
574 Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
575 api, err := cmdenv.GetApi(env, req)
576 if err != nil {
577 return err
578 }
579
580 showid, _ := req.Options["id"].(bool)
581 self, err := api.Key().Self(req.Context)
582 if err != nil {
583 return err
584 }
585
586 maddrs, err := api.Swarm().LocalAddrs(req.Context)
587 if err != nil {
588 return err
589 }
590
591 var addrs []string
592 p2pProtocolName := ma.ProtocolWithCode(ma.P_P2P).Name
593 for _, addr := range maddrs {
594 saddr := addr.String()
595 if showid {
596 saddr = path.Join(saddr, p2pProtocolName, self.ID().String())
597 }
598 addrs = append(addrs, saddr)
599 }
600 slices.Sort(addrs)
601 return cmds.EmitOnce(res, &stringList{addrs})
602 },
603 Type: stringList{},
604 Encoders: cmds.EncoderMap{
605 cmds.Text: cmds.MakeTypedEncoder(safeTextListEncoder),
606 },
607 }
608
609 var swarmAddrsListenCmd = &cmds.Command{
610 Helptext: cmds.HelpText{
611 Tagline: "List interface listening addresses.",
612 ShortDescription: `
613 'ipfs swarm addrs listen' lists all interface addresses the node is listening on.
614 `,
615 },
616 Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
617 api, err := cmdenv.GetApi(env, req)
618 if err != nil {
619 return err
620 }
621
622 var addrs []string
623 maddrs, err := api.Swarm().ListenAddrs(req.Context)
624 if err != nil {
625 return err
626 }
627
628 for _, addr := range maddrs {
629 addrs = append(addrs, addr.String())
630 }
631 slices.Sort(addrs)
632
633 return cmds.EmitOnce(res, &stringList{addrs})
634 },
635 Type: stringList{},
636 Encoders: cmds.EncoderMap{
637 cmds.Text: cmds.MakeTypedEncoder(safeTextListEncoder),
638 },
639 }
640
641 var swarmConnectCmd = &cmds.Command{
642 Helptext: cmds.HelpText{
643 Tagline: "Open connection to a given peer.",
644 ShortDescription: `
645 'ipfs swarm connect' attempts to ensure a connection to a given peer.
646
647 Multiaddresses given are advisory, for example the node may already be aware of other addresses for a given peer or may already have an established connection to the peer.
648
649 The address format is a libp2p multiaddr:
650
651 ipfs swarm connect /ip4/104.131.131.82/tcp/4001/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ
652 `,
653 },
654 Arguments: []cmds.Argument{
655 cmds.StringArg("address", true, true, "Address of peer to connect to.").EnableStdin(),
656 },
657 Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
658 node, err := cmdenv.GetNode(env)
659 if err != nil {
660 return err
661 }
662
663 api, err := cmdenv.GetApi(env, req)
664 if err != nil {
665 return err
666 }
667
668 addrs := req.Arguments
669
670 pis, err := parseAddresses(req.Context, addrs, node.DNSResolver)
671 if err != nil {
672 return err
673 }
674
675 output := make([]string, len(pis))
676 for i, pi := range pis {
677 output[i] = "connect " + pi.ID.String()
678
679 err := api.Swarm().Connect(req.Context, pi)
680 if err != nil {
681 return fmt.Errorf("%s failure: %s", output[i], err)
682 }
683 output[i] += " success"
684 }
685
686 return cmds.EmitOnce(res, &stringList{output})
687 },
688 Encoders: cmds.EncoderMap{
689 cmds.Text: cmds.MakeTypedEncoder(safeTextListEncoder),
690 },
691 Type: stringList{},
692 }
693
694 var swarmDisconnectCmd = &cmds.Command{
695 Helptext: cmds.HelpText{
696 Tagline: "Close connection to a given address.",
697 ShortDescription: `
698 'ipfs swarm disconnect' closes a connection to a peer address. The address
699 format is an IPFS multiaddr:
700
701 ipfs swarm disconnect /ip4/104.131.131.82/tcp/4001/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ
702
703 The disconnect is not permanent; if ipfs needs to talk to that address later,
704 it will reconnect.
705 `,
706 },
707 Arguments: []cmds.Argument{
708 cmds.StringArg("address", true, true, "Address of peer to disconnect from.").EnableStdin(),
709 },
710 Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
711 node, err := cmdenv.GetNode(env)
712 if err != nil {
713 return err
714 }
715
716 api, err := cmdenv.GetApi(env, req)
717 if err != nil {
718 return err
719 }
720
721 addrs, err := parseAddresses(req.Context, req.Arguments, node.DNSResolver)
722 if err != nil {
723 return err
724 }
725
726 output := make([]string, 0, len(addrs))
727 for _, ainfo := range addrs {
728 maddrs, err := peer.AddrInfoToP2pAddrs(&ainfo)
729 if err != nil {
730 return err
731 }
732 // FIXME: This will print:
733 //
734 // disconnect QmFoo success
735 // disconnect QmFoo success
736 // ...
737 //
738 // Once per address specified. However, I'm not sure of
739 // a good backwards compat solution. Right now, I'm just
740 // preserving the current behavior.
741 for _, addr := range maddrs {
742 msg := "disconnect " + ainfo.ID.String()
743 if err := api.Swarm().Disconnect(req.Context, addr); err != nil {
744 msg += " failure: " + err.Error()
745 } else {
746 msg += " success"
747 }
748 output = append(output, msg)
749 }
750 }
751 return cmds.EmitOnce(res, &stringList{output})
752 },
753 Encoders: cmds.EncoderMap{
754 cmds.Text: cmds.MakeTypedEncoder(safeTextListEncoder),
755 },
756 Type: stringList{},
757 }
758
759 // parseAddresses is a function that takes in a slice of string peer addresses
760 // (multiaddr + peerid) and returns a slice of properly constructed peers
761 func parseAddresses(ctx context.Context, addrs []string, rslv *madns.Resolver) ([]peer.AddrInfo, error) {
762 // resolve addresses
763 maddrs, err := resolveAddresses(ctx, addrs, rslv)
764 if err != nil {
765 return nil, err
766 }
767
768 return peer.AddrInfosFromP2pAddrs(maddrs...)
769 }
770
771 // resolveAddresses resolves addresses parallelly
772 func resolveAddresses(ctx context.Context, addrs []string, rslv *madns.Resolver) ([]ma.Multiaddr, error) {
773 ctx, cancel := context.WithTimeout(ctx, dnsResolveTimeout)
774 defer cancel()
775
776 var maddrs []ma.Multiaddr
777 var wg sync.WaitGroup
778 resolveErrC := make(chan error, len(addrs))
779
780 maddrC := make(chan ma.Multiaddr)
781
782 for _, addr := range addrs {
783 maddr, err := ma.NewMultiaddr(addr)
784 if err != nil {
785 return nil, err
786 }
787
788 // check whether address ends in `ipfs/Qm...`
789 if _, last := ma.SplitLast(maddr); last.Protocol().Code == ma.P_IPFS {
790 maddrs = append(maddrs, maddr)
791 continue
792 }
793 wg.Add(1)
794 go func(maddr ma.Multiaddr) {
795 defer wg.Done()
796 raddrs, err := rslv.Resolve(ctx, maddr)
797 if err != nil {
798 resolveErrC <- err
799 return
800 }
801 // filter out addresses that still doesn't end in `ipfs/Qm...`
802 found := 0
803 for _, raddr := range raddrs {
804 if _, last := ma.SplitLast(raddr); last != nil && last.Protocol().Code == ma.P_IPFS {
805 maddrC <- raddr
806 found++
807 }
808 }
809 if found == 0 {
810 resolveErrC <- fmt.Errorf("found no ipfs peers at %s", maddr)
811 }
812 }(maddr)
813 }
814 go func() {
815 wg.Wait()
816 close(maddrC)
817 }()
818
819 for maddr := range maddrC {
820 maddrs = append(maddrs, maddr)
821 }
822
823 select {
824 case err := <-resolveErrC:
825 return nil, err
826 default:
827 }
828
829 return maddrs, nil
830 }
831
832 var swarmFiltersCmd = &cmds.Command{
833 Helptext: cmds.HelpText{
834 Tagline: "Manipulate address filters.",
835 ShortDescription: `
836 'ipfs swarm filters' will list out currently applied filters. Its subcommands
837 can be used to add or remove said filters. Filters are specified using the
838 multiaddr-filter format:
839
840 Example:
841
842 /ip4/192.168.0.0/ipcidr/16
843
844 Where the above is equivalent to the standard CIDR:
845
846 192.168.0.0/16
847
848 Filters default to those specified under the "Swarm.AddrFilters" config key.
849 `,
850 },
851 Subcommands: map[string]*cmds.Command{
852 "add": swarmFiltersAddCmd,
853 "rm": swarmFiltersRmCmd,
854 },
855 Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
856 n, err := cmdenv.GetNode(env)
857 if err != nil {
858 return err
859 }
860
861 if !n.IsOnline {
862 return ErrNotOnline
863 }
864
865 var output []string
866 for _, f := range n.Filters.FiltersForAction(ma.ActionDeny) {
867 s, err := mamask.ConvertIPNet(&f)
868 if err != nil {
869 return err
870 }
871 output = append(output, s)
872 }
873 return cmds.EmitOnce(res, &stringList{output})
874 },
875 Encoders: cmds.EncoderMap{
876 cmds.Text: cmds.MakeTypedEncoder(safeTextListEncoder),
877 },
878 Type: stringList{},
879 }
880
881 var swarmFiltersAddCmd = &cmds.Command{
882 Helptext: cmds.HelpText{
883 Tagline: "Add an address filter.",
884 ShortDescription: `
885 'ipfs swarm filters add' will add an address filter to the daemons swarm.
886 `,
887 },
888 Arguments: []cmds.Argument{
889 cmds.StringArg("address", true, true, "Multiaddr to filter.").EnableStdin(),
890 },
891 Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
892 n, err := cmdenv.GetNode(env)
893 if err != nil {
894 return err
895 }
896
897 if !n.IsOnline {
898 return ErrNotOnline
899 }
900
901 if len(req.Arguments) == 0 {
902 return errors.New("no filters to add")
903 }
904
905 r, err := fsrepo.Open(env.(*commands.Context).ConfigRoot)
906 if err != nil {
907 return err
908 }
909 defer r.Close()
910 cfg, err := r.Config()
911 if err != nil {
912 return err
913 }
914
915 for _, arg := range req.Arguments {
916 mask, err := mamask.NewMask(arg)
917 if err != nil {
918 return err
919 }
920
921 n.Filters.AddFilter(*mask, ma.ActionDeny)
922 }
923
924 added, err := filtersAdd(r, cfg, req.Arguments)
925 if err != nil {
926 return err
927 }
928
929 return cmds.EmitOnce(res, &stringList{added})
930 },
931 Encoders: cmds.EncoderMap{
932 cmds.Text: cmds.MakeTypedEncoder(safeTextListEncoder),
933 },
934 Type: stringList{},
935 }
936
937 var swarmFiltersRmCmd = &cmds.Command{
938 Helptext: cmds.HelpText{
939 Tagline: "Remove an address filter.",
940 ShortDescription: `
941 'ipfs swarm filters rm' will remove an address filter from the daemons swarm.
942 `,
943 },
944 Arguments: []cmds.Argument{
945 cmds.StringArg("address", true, true, "Multiaddr filter to remove.").EnableStdin(),
946 },
947 Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
948 n, err := cmdenv.GetNode(env)
949 if err != nil {
950 return err
951 }
952
953 if !n.IsOnline {
954 return ErrNotOnline
955 }
956
957 r, err := fsrepo.Open(env.(*commands.Context).ConfigRoot)
958 if err != nil {
959 return err
960 }
961 defer r.Close()
962 cfg, err := r.Config()
963 if err != nil {
964 return err
965 }
966
967 if req.Arguments[0] == "all" || req.Arguments[0] == "*" {
968 fs := n.Filters.FiltersForAction(ma.ActionDeny)
969 for _, f := range fs {
970 n.Filters.RemoveLiteral(f)
971 }
972
973 removed, err := filtersRemoveAll(r, cfg)
974 if err != nil {
975 return err
976 }
977
978 return cmds.EmitOnce(res, &stringList{removed})
979 }
980
981 for _, arg := range req.Arguments {
982 mask, err := mamask.NewMask(arg)
983 if err != nil {
984 return err
985 }
986
987 n.Filters.RemoveLiteral(*mask)
988 }
989
990 removed, err := filtersRemove(r, cfg, req.Arguments)
991 if err != nil {
992 return err
993 }
994
995 return cmds.EmitOnce(res, &stringList{removed})
996 },
997 Encoders: cmds.EncoderMap{
998 cmds.Text: cmds.MakeTypedEncoder(safeTextListEncoder),
999 },
1000 Type: stringList{},
1001 }
1002
1003 func filtersAdd(r repo.Repo, cfg *config.Config, filters []string) ([]string, error) {
1004 addedMap := map[string]struct{}{}
1005 addedList := make([]string, 0, len(filters))
1006
1007 // re-add cfg swarm filters to rm dupes
1008 oldFilters := cfg.Swarm.AddrFilters
1009 cfg.Swarm.AddrFilters = nil
1010
1011 // add new filters
1012 for _, filter := range filters {
1013 if _, found := addedMap[filter]; found {
1014 continue
1015 }
1016
1017 cfg.Swarm.AddrFilters = append(cfg.Swarm.AddrFilters, filter)
1018 addedList = append(addedList, filter)
1019 addedMap[filter] = struct{}{}
1020 }
1021
1022 // add back original filters. in this order so that we output them.
1023 for _, filter := range oldFilters {
1024 if _, found := addedMap[filter]; found {
1025 continue
1026 }
1027
1028 cfg.Swarm.AddrFilters = append(cfg.Swarm.AddrFilters, filter)
1029 addedMap[filter] = struct{}{}
1030 }
1031
1032 if err := r.SetConfig(cfg); err != nil {
1033 return nil, err
1034 }
1035
1036 return addedList, nil
1037 }
1038
1039 func filtersRemoveAll(r repo.Repo, cfg *config.Config) ([]string, error) {
1040 removed := cfg.Swarm.AddrFilters
1041 cfg.Swarm.AddrFilters = nil
1042
1043 if err := r.SetConfig(cfg); err != nil {
1044 return nil, err
1045 }
1046
1047 return removed, nil
1048 }
1049
1050 func filtersRemove(r repo.Repo, cfg *config.Config, toRemoveFilters []string) ([]string, error) {
1051 removed := make([]string, 0, len(toRemoveFilters))
1052 keep := make([]string, 0, len(cfg.Swarm.AddrFilters))
1053
1054 oldFilters := cfg.Swarm.AddrFilters
1055
1056 for _, oldFilter := range oldFilters {
1057 found := false
1058 for _, toRemoveFilter := range toRemoveFilters {
1059 if oldFilter == toRemoveFilter {
1060 found = true
1061 removed = append(removed, toRemoveFilter)
1062 break
1063 }
1064 }
1065
1066 if !found {
1067 keep = append(keep, oldFilter)
1068 }
1069 }
1070 cfg.Swarm.AddrFilters = keep
1071
1072 if err := r.SetConfig(cfg); err != nil {
1073 return nil, err
1074 }
1075
1076 return removed, nil
1077 }