master
go 217 lines 5.83 KB
Raw
1 package commands
2
3 import (
4 "encoding/base64"
5 "encoding/json"
6 "errors"
7 "fmt"
8 "io"
9 "slices"
10 "strings"
11
12 version "github.com/ipfs/kubo"
13 "github.com/ipfs/kubo/core"
14 "github.com/ipfs/kubo/core/commands/cmdenv"
15 "github.com/ipfs/kubo/core/commands/cmdutils"
16
17 cmds "github.com/ipfs/go-ipfs-cmds"
18 ke "github.com/ipfs/kubo/core/commands/keyencode"
19 kb "github.com/libp2p/go-libp2p-kbucket"
20 ic "github.com/libp2p/go-libp2p/core/crypto"
21 "github.com/libp2p/go-libp2p/core/host"
22 "github.com/libp2p/go-libp2p/core/peer"
23 pstore "github.com/libp2p/go-libp2p/core/peerstore"
24 "github.com/libp2p/go-libp2p/core/protocol"
25 )
26
27 const offlineIDErrorMessage = "'ipfs id' cannot query information on remote peers without a running daemon; if you only want to convert --peerid-base, pass --offline option"
28
29 type IdOutput struct { // nolint
30 ID string
31 PublicKey string
32 Addresses []string
33 AgentVersion string
34 Protocols []protocol.ID
35 }
36
37 const (
38 formatOptionName = "format"
39 idFormatOptionName = "peerid-base"
40 )
41
42 var IDCmd = &cmds.Command{
43 Helptext: cmds.HelpText{
44 Tagline: "Show IPFS node id info.",
45 ShortDescription: `
46 Prints out information about the specified peer.
47 If no peer is specified, prints out information for local peers.
48
49 'ipfs id' supports the format option for output with the following keys:
50 <id> : The peers id.
51 <aver>: Agent version.
52 <pver>: Protocol version.
53 <pubkey>: Public key.
54 <addrs>: Addresses (newline delimited).
55 <protocols>: Libp2p Protocol registrations (newline delimited).
56
57 EXAMPLE:
58
59 ipfs id Qmece2RkXhsKe5CRooNisBTh4SK119KrXXGmoK6V3kb8aH -f="<addrs>\n"
60 `,
61 },
62 Arguments: []cmds.Argument{
63 cmds.StringArg("peerid", false, false, "Peer.ID of node to look up."),
64 },
65 Options: []cmds.Option{
66 cmds.StringOption(formatOptionName, "f", "Optional output format."),
67 cmds.StringOption(idFormatOptionName, "Encoding used for peer IDs: Can either be a multibase encoded CID or a base58btc encoded multihash. Takes {b58mh|base36|k|base32|b...}.").WithDefault("b58mh"),
68 },
69 Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
70 keyEnc, err := ke.KeyEncoderFromString(req.Options[idFormatOptionName].(string))
71 if err != nil {
72 return err
73 }
74
75 n, err := cmdenv.GetNode(env)
76 if err != nil {
77 return err
78 }
79
80 var id peer.ID
81 if len(req.Arguments) > 0 {
82 var err error
83 id, err = peer.Decode(req.Arguments[0])
84 if err != nil {
85 return errors.New("invalid peer id")
86 }
87 } else {
88 id = n.Identity
89 }
90
91 if id == n.Identity {
92 output, err := printSelf(keyEnc, n)
93 if err != nil {
94 return err
95 }
96 return cmds.EmitOnce(res, output)
97 }
98
99 offline, _ := req.Options[OfflineOption].(bool)
100 if !offline && !n.IsOnline {
101 return errors.New(offlineIDErrorMessage)
102 }
103
104 if !offline {
105 // We need to actually connect to run identify.
106 err = n.PeerHost.Connect(req.Context, peer.AddrInfo{ID: id})
107 switch err {
108 case nil:
109 case kb.ErrLookupFailure:
110 return errors.New(offlineIDErrorMessage)
111 default:
112 return err
113 }
114 }
115
116 output, err := printPeer(keyEnc, n.Peerstore, id)
117 if err != nil {
118 return err
119 }
120 return cmds.EmitOnce(res, output)
121 },
122 Encoders: cmds.EncoderMap{
123 cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, out *IdOutput) error {
124 format, found := req.Options[formatOptionName].(string)
125 if found {
126 output := format
127 output = strings.Replace(output, "<id>", out.ID, -1)
128 output = strings.Replace(output, "<aver>", out.AgentVersion, -1)
129 output = strings.Replace(output, "<pubkey>", out.PublicKey, -1)
130 output = strings.Replace(output, "<addrs>", strings.Join(out.Addresses, "\n"), -1)
131 output = strings.Replace(output, "<protocols>", strings.Join(protocol.ConvertToStrings(out.Protocols), "\n"), -1)
132 output = strings.Replace(output, "\\n", "\n", -1)
133 output = strings.Replace(output, "\\t", "\t", -1)
134 fmt.Fprint(w, output)
135 } else {
136 marshaled, err := json.MarshalIndent(out, "", "\t")
137 if err != nil {
138 return err
139 }
140 marshaled = append(marshaled, byte('\n'))
141 fmt.Fprintln(w, string(marshaled))
142 }
143 return nil
144 }),
145 },
146 Type: IdOutput{},
147 }
148
149 func printPeer(keyEnc ke.KeyEncoder, ps pstore.Peerstore, p peer.ID) (any, error) {
150 if p == "" {
151 return nil, errors.New("attempted to print nil peer")
152 }
153
154 info := new(IdOutput)
155 info.ID = keyEnc.FormatID(p)
156
157 if pk := ps.PubKey(p); pk != nil {
158 pkb, err := ic.MarshalPublicKey(pk)
159 if err != nil {
160 return nil, err
161 }
162 info.PublicKey = base64.StdEncoding.EncodeToString(pkb)
163 }
164
165 addrInfo := ps.PeerInfo(p)
166 addrs, err := peer.AddrInfoToP2pAddrs(&addrInfo)
167 if err != nil {
168 return nil, err
169 }
170
171 for _, a := range addrs {
172 info.Addresses = append(info.Addresses, a.String())
173 }
174 slices.Sort(info.Addresses)
175
176 protocols, _ := ps.GetProtocols(p) // don't care about errors here.
177 for _, proto := range protocols {
178 info.Protocols = append(info.Protocols, protocol.ID(cmdutils.CleanAndTrim(string(proto))))
179 }
180 slices.Sort(info.Protocols)
181
182 if v, err := ps.Get(p, "AgentVersion"); err == nil {
183 if vs, ok := v.(string); ok {
184 info.AgentVersion = cmdutils.CleanAndTrim(vs)
185 }
186 }
187
188 return info, nil
189 }
190
191 // printing self is special cased as we get values differently.
192 func printSelf(keyEnc ke.KeyEncoder, node *core.IpfsNode) (any, error) {
193 info := new(IdOutput)
194 info.ID = keyEnc.FormatID(node.Identity)
195
196 pk := node.PrivateKey.GetPublic()
197 pkb, err := ic.MarshalPublicKey(pk)
198 if err != nil {
199 return nil, err
200 }
201 info.PublicKey = base64.StdEncoding.EncodeToString(pkb)
202
203 if node.PeerHost != nil {
204 addrs, err := peer.AddrInfoToP2pAddrs(host.InfoFromHost(node.PeerHost))
205 if err != nil {
206 return nil, err
207 }
208 for _, a := range addrs {
209 info.Addresses = append(info.Addresses, a.String())
210 }
211 slices.Sort(info.Addresses)
212 info.Protocols = node.PeerHost.Mux().Protocols()
213 slices.Sort(info.Protocols)
214 }
215 info.AgentVersion = version.GetUserAgentVersion()
216 return info, nil
217 }