2
3
import (
4
"context"
5
- "encoding/base64"
5
"errors"
6
"fmt"
7
"io"
9
- "time"
8
11
- cmdenv "github.com/ipfs/kubo/core/commands/cmdenv"
12
-
13
- cid "github.com/ipfs/go-cid"
9
cmds "github.com/ipfs/go-ipfs-cmds"
15
- ipld "github.com/ipfs/go-ipld-format"
16
- dag "github.com/ipfs/go-merkledag"
17
- path "github.com/ipfs/go-path"
10
+ "github.com/ipfs/kubo/core/commands/cmdenv"
11
peer "github.com/libp2p/go-libp2p-core/peer"
12
routing "github.com/libp2p/go-libp2p-core/routing"
13
)
14
15
var ErrNotDHT = errors.New("routing service is not a DHT")
16
24
-// TODO: Factor into `ipfs dht` and `ipfs routing`.
25
-// Everything *except `query` goes into `ipfs routing`.
26
-
17
var DhtCmd = &cmds.Command{
18
Helptext: cmds.HelpText{
19
Tagline: "Issue commands directly through the DHT.",
30
},
31
}
32
43
-const (
44
- dhtVerboseOptionName = "verbose"
45
-)
33
+var findProvidersDhtCmd = &cmds.Command{
34
+ Helptext: findProvidersRoutingCmd.Helptext,
35
+ Arguments: findProvidersRoutingCmd.Arguments,
36
+ Options: findProvidersRoutingCmd.Options,
37
+ Run: findProvidersRoutingCmd.Run,
38
+ Encoders: findProvidersRoutingCmd.Encoders,
39
+ Type: findProvidersRoutingCmd.Type,
40
+ Status: cmds.Deprecated,
41
+}
42
+
43
+var findPeerDhtCmd = &cmds.Command{
44
+ Helptext: findPeerRoutingCmd.Helptext,
45
+ Arguments: findPeerRoutingCmd.Arguments,
46
+ Options: findPeerRoutingCmd.Options,
47
+ Run: findPeerRoutingCmd.Run,
48
+ Encoders: findPeerRoutingCmd.Encoders,
49
+ Type: findPeerRoutingCmd.Type,
50
+ Status: cmds.Deprecated,
51
+}
52
+
53
+var getValueDhtCmd = &cmds.Command{
54
+ Helptext: getValueRoutingCmd.Helptext,
55
+ Arguments: getValueRoutingCmd.Arguments,
56
+ Options: getValueRoutingCmd.Options,
57
+ Run: getValueRoutingCmd.Run,
58
+ Encoders: getValueRoutingCmd.Encoders,
59
+ Type: getValueRoutingCmd.Type,
60
+ Status: cmds.Deprecated,
61
+}
62
+
63
+var putValueDhtCmd = &cmds.Command{
64
+ Helptext: putValueRoutingCmd.Helptext,
65
+ Arguments: putValueRoutingCmd.Arguments,
66
+ Options: putValueRoutingCmd.Options,
67
+ Run: putValueRoutingCmd.Run,
68
+ Encoders: putValueRoutingCmd.Encoders,
69
+ Type: putValueRoutingCmd.Type,
70
+ Status: cmds.Deprecated,
71
+}
72
+
73
+var provideRefDhtCmd = &cmds.Command{
74
+ Helptext: provideRefRoutingCmd.Helptext,
75
+ Arguments: provideRefRoutingCmd.Arguments,
76
+ Options: provideRefRoutingCmd.Options,
77
+ Run: provideRefRoutingCmd.Run,
78
+ Encoders: provideRefRoutingCmd.Encoders,
79
+ Type: provideRefRoutingCmd.Type,
80
+ Status: cmds.Deprecated,
81
+}
82
83
// kademlia extends the routing interface with a command to get the peers closest to the target
84
type kademlia interface {
169
},
170
Type: routing.QueryEvent{},
171
}
136
-
137
-const (
138
- numProvidersOptionName = "num-providers"
139
-)
140
-
141
-var findProvidersDhtCmd = &cmds.Command{
142
- Helptext: cmds.HelpText{
143
- Tagline: "Find peers that can provide a specific value, given a key.",
144
- ShortDescription: "Outputs a list of newline-delimited provider Peer IDs.",
145
- },
146
-
147
- Arguments: []cmds.Argument{
148
- cmds.StringArg("key", true, true, "The key to find providers for."),
149
- },
150
- Options: []cmds.Option{
151
- cmds.BoolOption(dhtVerboseOptionName, "v", "Print extra information."),
152
- cmds.IntOption(numProvidersOptionName, "n", "The number of providers to find.").WithDefault(20),
153
- },
154
- Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
155
- n, err := cmdenv.GetNode(env)
156
- if err != nil {
157
- return err
158
- }
159
-
160
- if !n.IsOnline {
161
- return ErrNotOnline
162
- }
163
-
164
- numProviders, _ := req.Options[numProvidersOptionName].(int)
165
- if numProviders < 1 {
166
- return fmt.Errorf("number of providers must be greater than 0")
167
- }
168
-
169
- c, err := cid.Parse(req.Arguments[0])
170
-
171
- if err != nil {
172
- return err
173
- }
174
-
175
- ctx, cancel := context.WithCancel(req.Context)
176
- ctx, events := routing.RegisterForQueryEvents(ctx)
177
-
178
- pchan := n.Routing.FindProvidersAsync(ctx, c, numProviders)
179
-
180
- go func() {
181
- defer cancel()
182
- for p := range pchan {
183
- np := p
184
- routing.PublishQueryEvent(ctx, &routing.QueryEvent{
185
- Type: routing.Provider,
186
- Responses: []*peer.AddrInfo{&np},
187
- })
188
- }
189
- }()
190
- for e := range events {
191
- if err := res.Emit(e); err != nil {
192
- return err
193
- }
194
- }
195
-
196
- return nil
197
- },
198
- Encoders: cmds.EncoderMap{
199
- cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, out *routing.QueryEvent) error {
200
- pfm := pfuncMap{
201
- routing.FinalPeer: func(obj *routing.QueryEvent, out io.Writer, verbose bool) error {
202
- if verbose {
203
- fmt.Fprintf(out, "* closest peer %s\n", obj.ID)
204
- }
205
- return nil
206
- },
207
- routing.Provider: func(obj *routing.QueryEvent, out io.Writer, verbose bool) error {
208
- prov := obj.Responses[0]
209
- if verbose {
210
- fmt.Fprintf(out, "provider: ")
211
- }
212
- fmt.Fprintf(out, "%s\n", prov.ID.Pretty())
213
- if verbose {
214
- for _, a := range prov.Addrs {
215
- fmt.Fprintf(out, "\t%s\n", a)
216
- }
217
- }
218
- return nil
219
- },
220
- }
221
-
222
- verbose, _ := req.Options[dhtVerboseOptionName].(bool)
223
- return printEvent(out, w, verbose, pfm)
224
- }),
225
- },
226
- Type: routing.QueryEvent{},
227
-}
228
-
229
-const (
230
- recursiveOptionName = "recursive"
231
-)
232
-
233
-var provideRefDhtCmd = &cmds.Command{
234
- Helptext: cmds.HelpText{
235
- Tagline: "Announce to the network that you are providing given values.",
236
- },
237
-
238
- Arguments: []cmds.Argument{
239
- cmds.StringArg("key", true, true, "The key[s] to send provide records for.").EnableStdin(),
240
- },
241
- Options: []cmds.Option{
242
- cmds.BoolOption(dhtVerboseOptionName, "v", "Print extra information."),
243
- cmds.BoolOption(recursiveOptionName, "r", "Recursively provide entire graph."),
244
- },
245
- Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
246
- nd, err := cmdenv.GetNode(env)
247
- if err != nil {
248
- return err
249
- }
250
-
251
- if !nd.IsOnline {
252
- return ErrNotOnline
253
- }
254
-
255
- if len(nd.PeerHost.Network().Conns()) == 0 {
256
- return errors.New("cannot provide, no connected peers")
257
- }
258
-
259
- // Needed to parse stdin args.
260
- // TODO: Lazy Load
261
- err = req.ParseBodyArgs()
262
- if err != nil {
263
- return err
264
- }
265
-
266
- rec, _ := req.Options[recursiveOptionName].(bool)
267
-
268
- var cids []cid.Cid
269
- for _, arg := range req.Arguments {
270
- c, err := cid.Decode(arg)
271
- if err != nil {
272
- return err
273
- }
274
-
275
- has, err := nd.Blockstore.Has(req.Context, c)
276
- if err != nil {
277
- return err
278
- }
279
-
280
- if !has {
281
- return fmt.Errorf("block %s not found locally, cannot provide", c)
282
- }
283
-
284
- cids = append(cids, c)
285
- }
286
-
287
- ctx, cancel := context.WithCancel(req.Context)
288
- ctx, events := routing.RegisterForQueryEvents(ctx)
289
-
290
- var provideErr error
291
- go func() {
292
- defer cancel()
293
- if rec {
294
- provideErr = provideKeysRec(ctx, nd.Routing, nd.DAG, cids)
295
- } else {
296
- provideErr = provideKeys(ctx, nd.Routing, cids)
297
- }
298
- if provideErr != nil {
299
- routing.PublishQueryEvent(ctx, &routing.QueryEvent{
300
- Type: routing.QueryError,
301
- Extra: provideErr.Error(),
302
- })
303
- }
304
- }()
305
-
306
- for e := range events {
307
- if err := res.Emit(e); err != nil {
308
- return err
309
- }
310
- }
311
-
312
- return provideErr
313
- },
314
- Encoders: cmds.EncoderMap{
315
- cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, out *routing.QueryEvent) error {
316
- pfm := pfuncMap{
317
- routing.FinalPeer: func(obj *routing.QueryEvent, out io.Writer, verbose bool) error {
318
- if verbose {
319
- fmt.Fprintf(out, "sending provider record to peer %s\n", obj.ID)
320
- }
321
- return nil
322
- },
323
- }
324
-
325
- verbose, _ := req.Options[dhtVerboseOptionName].(bool)
326
- return printEvent(out, w, verbose, pfm)
327
- }),
328
- },
329
- Type: routing.QueryEvent{},
330
-}
331
-
332
-func provideKeys(ctx context.Context, r routing.Routing, cids []cid.Cid) error {
333
- for _, c := range cids {
334
- err := r.Provide(ctx, c, true)
335
- if err != nil {
336
- return err
337
- }
338
- }
339
- return nil
340
-}
341
-
342
-func provideKeysRec(ctx context.Context, r routing.Routing, dserv ipld.DAGService, cids []cid.Cid) error {
343
- provided := cid.NewSet()
344
- for _, c := range cids {
345
- kset := cid.NewSet()
346
-
347
- err := dag.Walk(ctx, dag.GetLinksDirect(dserv), c, kset.Visit)
348
- if err != nil {
349
- return err
350
- }
351
-
352
- for _, k := range kset.Keys() {
353
- if provided.Has(k) {
354
- continue
355
- }
356
-
357
- err = r.Provide(ctx, k, true)
358
- if err != nil {
359
- return err
360
- }
361
- provided.Add(k)
362
- }
363
- }
364
-
365
- return nil
366
-}
367
-
368
-var findPeerDhtCmd = &cmds.Command{
369
- Helptext: cmds.HelpText{
370
- Tagline: "Find the multiaddresses associated with a Peer ID.",
371
- ShortDescription: "Outputs a list of newline-delimited multiaddresses.",
372
- },
373
-
374
- Arguments: []cmds.Argument{
375
- cmds.StringArg("peerID", true, true, "The ID of the peer to search for."),
376
- },
377
- Options: []cmds.Option{
378
- cmds.BoolOption(dhtVerboseOptionName, "v", "Print extra information."),
379
- },
380
- Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
381
- nd, err := cmdenv.GetNode(env)
382
- if err != nil {
383
- return err
384
- }
385
-
386
- if !nd.IsOnline {
387
- return ErrNotOnline
388
- }
389
-
390
- pid, err := peer.Decode(req.Arguments[0])
391
- if err != nil {
392
- return err
393
- }
394
-
395
- ctx, cancel := context.WithCancel(req.Context)
396
- ctx, events := routing.RegisterForQueryEvents(ctx)
397
-
398
- var findPeerErr error
399
- go func() {
400
- defer cancel()
401
- var pi peer.AddrInfo
402
- pi, findPeerErr = nd.Routing.FindPeer(ctx, pid)
403
- if findPeerErr != nil {
404
- routing.PublishQueryEvent(ctx, &routing.QueryEvent{
405
- Type: routing.QueryError,
406
- Extra: findPeerErr.Error(),
407
- })
408
- return
409
- }
410
-
411
- routing.PublishQueryEvent(ctx, &routing.QueryEvent{
412
- Type: routing.FinalPeer,
413
- Responses: []*peer.AddrInfo{&pi},
414
- })
415
- }()
416
-
417
- for e := range events {
418
- if err := res.Emit(e); err != nil {
419
- return err
420
- }
421
- }
422
-
423
- return findPeerErr
424
- },
425
- Encoders: cmds.EncoderMap{
426
- cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, out *routing.QueryEvent) error {
427
- pfm := pfuncMap{
428
- routing.FinalPeer: func(obj *routing.QueryEvent, out io.Writer, verbose bool) error {
429
- pi := obj.Responses[0]
430
- for _, a := range pi.Addrs {
431
- fmt.Fprintf(out, "%s\n", a)
432
- }
433
- return nil
434
- },
435
- }
436
-
437
- verbose, _ := req.Options[dhtVerboseOptionName].(bool)
438
- return printEvent(out, w, verbose, pfm)
439
- }),
440
- },
441
- Type: routing.QueryEvent{},
442
-}
443
-
444
-var getValueDhtCmd = &cmds.Command{
445
- Helptext: cmds.HelpText{
446
- Tagline: "Given a key, query the routing system for its best value.",
447
- ShortDescription: `
448
-Outputs the best value for the given key.
449
-
450
-There may be several different values for a given key stored in the routing
451
-system; in this context 'best' means the record that is most desirable. There is
452
-no one metric for 'best': it depends entirely on the key type. For IPNS, 'best'
453
-is the record that is both valid and has the highest sequence number (freshest).
454
-Different key types can specify other 'best' rules.
455
-`,
456
- },
457
-
458
- Arguments: []cmds.Argument{
459
- cmds.StringArg("key", true, true, "The key to find a value for."),
460
- },
461
- Options: []cmds.Option{
462
- cmds.BoolOption(dhtVerboseOptionName, "v", "Print extra information."),
463
- },
464
- Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
465
- nd, err := cmdenv.GetNode(env)
466
- if err != nil {
467
- return err
468
- }
469
-
470
- if !nd.IsOnline {
471
- return ErrNotOnline
472
- }
473
-
474
- dhtkey, err := escapeDhtKey(req.Arguments[0])
475
- if err != nil {
476
- return err
477
- }
478
-
479
- ctx, cancel := context.WithCancel(req.Context)
480
- ctx, events := routing.RegisterForQueryEvents(ctx)
481
-
482
- var getErr error
483
- go func() {
484
- defer cancel()
485
- var val []byte
486
- val, getErr = nd.Routing.GetValue(ctx, dhtkey)
487
- if getErr != nil {
488
- routing.PublishQueryEvent(ctx, &routing.QueryEvent{
489
- Type: routing.QueryError,
490
- Extra: getErr.Error(),
491
- })
492
- } else {
493
- routing.PublishQueryEvent(ctx, &routing.QueryEvent{
494
- Type: routing.Value,
495
- Extra: base64.StdEncoding.EncodeToString(val),
496
- })
497
- }
498
- }()
499
-
500
- for e := range events {
501
- if err := res.Emit(e); err != nil {
502
- return err
503
- }
504
- }
505
-
506
- return getErr
507
- },
508
- Encoders: cmds.EncoderMap{
509
- cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, out *routing.QueryEvent) error {
510
- pfm := pfuncMap{
511
- routing.Value: func(obj *routing.QueryEvent, out io.Writer, verbose bool) error {
512
- if verbose {
513
- _, err := fmt.Fprintf(out, "got value: '%s'\n", obj.Extra)
514
- return err
515
- }
516
- res, err := base64.StdEncoding.DecodeString(obj.Extra)
517
- if err != nil {
518
- return err
519
- }
520
- _, err = out.Write(res)
521
- return err
522
- },
523
- }
524
-
525
- verbose, _ := req.Options[dhtVerboseOptionName].(bool)
526
- return printEvent(out, w, verbose, pfm)
527
- }),
528
- },
529
- Type: routing.QueryEvent{},
530
-}
531
-
532
-var putValueDhtCmd = &cmds.Command{
533
- Helptext: cmds.HelpText{
534
- Tagline: "Write a key/value pair to the routing system.",
535
- ShortDescription: `
536
-Given a key of the form /foo/bar and a valid value for that key, this will write
537
-that value to the routing system with that key.
538
-
539
-Keys have two parts: a keytype (foo) and the key name (bar). IPNS uses the
540
-/ipns keytype, and expects the key name to be a Peer ID. IPNS entries are
541
-specifically formatted (protocol buffer).
542
-
543
-You may only use keytypes that are supported in your ipfs binary: currently
544
-this is only /ipns. Unless you have a relatively deep understanding of the
545
-go-ipfs routing internals, you likely want to be using 'ipfs name publish' instead
546
-of this.
547
-
548
-The value must be a valid value for the given key type. For example, if the key
549
-is /ipns/QmFoo, the value must be IPNS record (protobuf) signed with the key
550
-identified by QmFoo.
551
-`,
552
- },
553
-
554
- Arguments: []cmds.Argument{
555
- cmds.StringArg("key", true, false, "The key to store the value at."),
556
- cmds.FileArg("value-file", true, false, "A path to a file containing the value to store.").EnableStdin(),
557
- },
558
- Options: []cmds.Option{
559
- cmds.BoolOption(dhtVerboseOptionName, "v", "Print extra information."),
560
- },
561
- Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
562
- nd, err := cmdenv.GetNode(env)
563
- if err != nil {
564
- return err
565
- }
566
-
567
- if !nd.IsOnline {
568
- return ErrNotOnline
569
- }
570
-
571
- key, err := escapeDhtKey(req.Arguments[0])
572
- if err != nil {
573
- return err
574
- }
575
-
576
- file, err := cmdenv.GetFileArg(req.Files.Entries())
577
- if err != nil {
578
- return err
579
- }
580
- defer file.Close()
581
-
582
- data, err := io.ReadAll(file)
583
- if err != nil {
584
- return err
585
- }
586
-
587
- ctx, cancel := context.WithCancel(req.Context)
588
- ctx, events := routing.RegisterForQueryEvents(ctx)
589
-
590
- var putErr error
591
- go func() {
592
- defer cancel()
593
- putErr = nd.Routing.PutValue(ctx, key, []byte(data))
594
- if putErr != nil {
595
- routing.PublishQueryEvent(ctx, &routing.QueryEvent{
596
- Type: routing.QueryError,
597
- Extra: putErr.Error(),
598
- })
599
- }
600
- }()
601
-
602
- for e := range events {
603
- if err := res.Emit(e); err != nil {
604
- return err
605
- }
606
- }
607
-
608
- return putErr
609
- },
610
- Encoders: cmds.EncoderMap{
611
- cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, out *routing.QueryEvent) error {
612
- pfm := pfuncMap{
613
- routing.FinalPeer: func(obj *routing.QueryEvent, out io.Writer, verbose bool) error {
614
- if verbose {
615
- fmt.Fprintf(out, "* closest peer %s\n", obj.ID)
616
- }
617
- return nil
618
- },
619
- routing.Value: func(obj *routing.QueryEvent, out io.Writer, verbose bool) error {
620
- fmt.Fprintf(out, "%s\n", obj.ID.Pretty())
621
- return nil
622
- },
623
- }
624
-
625
- verbose, _ := req.Options[dhtVerboseOptionName].(bool)
626
-
627
- return printEvent(out, w, verbose, pfm)
628
- }),
629
- },
630
- Type: routing.QueryEvent{},
631
-}
632
-
633
-type printFunc func(obj *routing.QueryEvent, out io.Writer, verbose bool) error
634
-type pfuncMap map[routing.QueryEventType]printFunc
635
-
636
-func printEvent(obj *routing.QueryEvent, out io.Writer, verbose bool, override pfuncMap) error {
637
- if verbose {
638
- fmt.Fprintf(out, "%s: ", time.Now().Format("15:04:05.000"))
639
- }
640
-
641
- if override != nil {
642
- if pf, ok := override[obj.Type]; ok {
643
- return pf(obj, out, verbose)
644
- }
645
- }
646
-
647
- switch obj.Type {
648
- case routing.SendingQuery:
649
- if verbose {
650
- fmt.Fprintf(out, "* querying %s\n", obj.ID)
651
- }
652
- case routing.Value:
653
- if verbose {
654
- fmt.Fprintf(out, "got value: '%s'\n", obj.Extra)
655
- } else {
656
- fmt.Fprint(out, obj.Extra)
657
- }
658
- case routing.PeerResponse:
659
- if verbose {
660
- fmt.Fprintf(out, "* %s says use ", obj.ID)
661
- for _, p := range obj.Responses {
662
- fmt.Fprintf(out, "%s ", p.ID)
663
- }
664
- fmt.Fprintln(out)
665
- }
666
- case routing.QueryError:
667
- if verbose {
668
- fmt.Fprintf(out, "error: %s\n", obj.Extra)
669
- }
670
- case routing.DialingPeer:
671
- if verbose {
672
- fmt.Fprintf(out, "dialing peer: %s\n", obj.ID)
673
- }
674
- case routing.AddingPeer:
675
- if verbose {
676
- fmt.Fprintf(out, "adding peer to query: %s\n", obj.ID)
677
- }
678
- case routing.FinalPeer:
679
- default:
680
- if verbose {
681
- fmt.Fprintf(out, "unrecognized event type: %d\n", obj.Type)
682
- }
683
- }
684
- return nil
685
-}
686
-
687
-func escapeDhtKey(s string) (string, error) {
688
- parts := path.SplitList(s)
689
- if len(parts) != 3 ||
690
- parts[0] != "" ||
691
- !(parts[1] == "ipns" || parts[1] == "pk") {
692
- return "", errors.New("invalid key")
693
- }
694
-
695
- k, err := peer.Decode(parts[2])
696
- if err != nil {
697
- return "", err
698
- }
699
- return path.Join(append(parts[:2], string(k))), nil
700
-}