id.go 6.06 KB
Newer Older
1 2 3 4 5 6
package commands

import (
	"encoding/base64"
	"encoding/json"
	"errors"
7
	"fmt"
8
	"io"
9
	"sort"
10
	"strings"
11

Steven Allen's avatar
Steven Allen committed
12
	version "github.com/ipfs/go-ipfs"
13
	core "github.com/ipfs/go-ipfs/core"
14
	cmdenv "github.com/ipfs/go-ipfs/core/commands/cmdenv"
Jeromy's avatar
Jeromy committed
15

Jakub Sztandera's avatar
Jakub Sztandera committed
16
	cmds "github.com/ipfs/go-ipfs-cmds"
17
	ke "github.com/ipfs/go-ipfs/core/commands/keyencode"
Raúl Kripalani's avatar
Raúl Kripalani committed
18
	ic "github.com/libp2p/go-libp2p-core/crypto"
Steven Allen's avatar
Steven Allen committed
19
	"github.com/libp2p/go-libp2p-core/host"
Raúl Kripalani's avatar
Raúl Kripalani committed
20 21
	peer "github.com/libp2p/go-libp2p-core/peer"
	pstore "github.com/libp2p/go-libp2p-core/peerstore"
Jakub Sztandera's avatar
Jakub Sztandera committed
22 23
	kb "github.com/libp2p/go-libp2p-kbucket"
	identify "github.com/libp2p/go-libp2p/p2p/protocol/identify"
24 25
)

26 27 28 29 30 31 32 33 34
const offlineIdErrorMessage = `'ipfs id' currently cannot query information on remote
peers without a running daemon; we are working to fix this.
In the meantime, if you want to query remote peers using 'ipfs id',
please run the daemon:

    ipfs daemon &
    ipfs id QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ
`

35 36 37 38 39 40
type IdOutput struct {
	ID              string
	PublicKey       string
	Addresses       []string
	AgentVersion    string
	ProtocolVersion string
41
	Protocols       []string
42 43
}

Kejie Zhang's avatar
Kejie Zhang committed
44
const (
45 46
	formatOptionName   = "format"
	idFormatOptionName = "peerid-base"
Kejie Zhang's avatar
Kejie Zhang committed
47 48
)

49
var IDCmd = &cmds.Command{
Steven Allen's avatar
Steven Allen committed
50
	Helptext: cmds.HelpText{
51
		Tagline: "Show ipfs node id info.",
Jeromy's avatar
Jeromy committed
52
		ShortDescription: `
Richard Littauer's avatar
Richard Littauer committed
53 54
Prints out information about the specified peer.
If no peer is specified, prints out information for local peers.
Jeromy's avatar
Jeromy committed
55

Richard Littauer's avatar
Richard Littauer committed
56 57 58 59 60 61
'ipfs id' supports the format option for output with the following keys:
<id> : The peers id.
<aver>: Agent version.
<pver>: Protocol version.
<pubkey>: Public key.
<addrs>: Addresses (newline delimited).
62 63 64 65

EXAMPLE:

    ipfs id Qmece2RkXhsKe5CRooNisBTh4SK119KrXXGmoK6V3kb8aH -f="<addrs>\n"
Jeromy's avatar
Jeromy committed
66
`,
67
	},
Steven Allen's avatar
Steven Allen committed
68 69
	Arguments: []cmds.Argument{
		cmds.StringArg("peerid", false, false, "Peer.ID of node to look up."),
70
	},
Steven Allen's avatar
Steven Allen committed
71 72
	Options: []cmds.Option{
		cmds.StringOption(formatOptionName, "f", "Optional output format."),
73
		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"),
74
	},
75
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
76 77 78 79 80
		keyEnc, err := ke.KeyEncoderFromString(req.Options[idFormatOptionName].(string))
		if err != nil {
			return err
		}

81
		n, err := cmdenv.GetNode(env)
82
		if err != nil {
83
			return err
84 85
		}

Jeromy's avatar
Jeromy committed
86
		var id peer.ID
87
		if len(req.Arguments) > 0 {
Steven Allen's avatar
Steven Allen committed
88
			var err error
89
			id, err = peer.Decode(req.Arguments[0])
Steven Allen's avatar
Steven Allen committed
90
			if err != nil {
91
				return fmt.Errorf("invalid peer id")
Jeromy's avatar
Jeromy committed
92 93
			}
		} else {
94
			id = n.Identity
Jeromy's avatar
Jeromy committed
95 96
		}

97
		if id == n.Identity {
98
			output, err := printSelf(keyEnc, n)
99
			if err != nil {
100
				return err
101
			}
102
			return cmds.EmitOnce(res, output)
103 104
		}

105
		// TODO handle offline mode with polymorphism instead of conditionals
106
		if !n.IsOnline {
107
			return errors.New(offlineIdErrorMessage)
108 109
		}

110 111 112 113 114
		// We need to actually connect to run identify.
		err = n.PeerHost.Connect(req.Context, peer.AddrInfo{ID: id})
		switch err {
		case nil:
		case kb.ErrLookupFailure:
115
			return errors.New(offlineIdErrorMessage)
116
		default:
117
			return err
118
		}
119

120
		output, err := printPeer(keyEnc, n.Peerstore, id)
121
		if err != nil {
122
			return err
123
		}
124
		return cmds.EmitOnce(res, output)
125
	},
126
	Encoders: cmds.EncoderMap{
127
		cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, out *IdOutput) error {
128
			format, found := req.Options[formatOptionName].(string)
129 130
			if found {
				output := format
131 132 133 134 135
				output = strings.Replace(output, "<id>", out.ID, -1)
				output = strings.Replace(output, "<aver>", out.AgentVersion, -1)
				output = strings.Replace(output, "<pver>", out.ProtocolVersion, -1)
				output = strings.Replace(output, "<pubkey>", out.PublicKey, -1)
				output = strings.Replace(output, "<addrs>", strings.Join(out.Addresses, "\n"), -1)
136
				output = strings.Replace(output, "<protocols>", strings.Join(out.Protocols, "\n"), -1)
137 138
				output = strings.Replace(output, "\\n", "\n", -1)
				output = strings.Replace(output, "\\t", "\t", -1)
139
				fmt.Fprint(w, output)
140
			} else {
141
				marshaled, err := json.MarshalIndent(out, "", "\t")
142
				if err != nil {
143
					return err
144
				}
145
				marshaled = append(marshaled, byte('\n'))
146
				fmt.Fprintln(w, string(marshaled))
147
			}
148 149
			return nil
		}),
150
	},
151
	Type: IdOutput{},
152 153
}

154
func printPeer(keyEnc ke.KeyEncoder, ps pstore.Peerstore, p peer.ID) (interface{}, error) {
155
	if p == "" {
Łukasz Magiera's avatar
Łukasz Magiera committed
156
		return nil, errors.New("attempted to print nil peer")
157
	}
158

159
	info := new(IdOutput)
160
	info.ID = keyEnc.FormatID(p)
161

162 163
	if pk := ps.PubKey(p); pk != nil {
		pkb, err := ic.MarshalPublicKey(pk)
Jeromy's avatar
Jeromy committed
164 165 166 167
		if err != nil {
			return nil, err
		}
		info.PublicKey = base64.StdEncoding.EncodeToString(pkb)
168
	}
169

170 171 172 173 174 175 176
	addrInfo := ps.PeerInfo(p)
	addrs, err := peer.AddrInfoToP2pAddrs(&addrInfo)
	if err != nil {
		return nil, err
	}

	for _, a := range addrs {
177 178
		info.Addresses = append(info.Addresses, a.String())
	}
179
	sort.Strings(info.Addresses)
180

181 182 183 184
	protocols, _ := ps.GetProtocols(p) // don't care about errors here.
	for _, p := range protocols {
		info.Protocols = append(info.Protocols, string(p))
	}
185
	sort.Strings(info.Protocols)
186

187 188
	if v, err := ps.Get(p, "ProtocolVersion"); err == nil {
		if vs, ok := v.(string); ok {
189
			info.ProtocolVersion = vs
190 191 192 193
		}
	}
	if v, err := ps.Get(p, "AgentVersion"); err == nil {
		if vs, ok := v.(string); ok {
194
			info.AgentVersion = vs
195 196
		}
	}
197 198 199

	return info, nil
}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
200 201

// printing self is special cased as we get values differently.
202
func printSelf(keyEnc ke.KeyEncoder, node *core.IpfsNode) (interface{}, error) {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
203
	info := new(IdOutput)
204
	info.ID = keyEnc.FormatID(node.Identity)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
205 206 207 208 209 210 211 212 213

	pk := node.PrivateKey.GetPublic()
	pkb, err := ic.MarshalPublicKey(pk)
	if err != nil {
		return nil, err
	}
	info.PublicKey = base64.StdEncoding.EncodeToString(pkb)

	if node.PeerHost != nil {
Steven Allen's avatar
Steven Allen committed
214 215 216 217 218 219
		addrs, err := peer.AddrInfoToP2pAddrs(host.InfoFromHost(node.PeerHost))
		if err != nil {
			return nil, err
		}
		for _, a := range addrs {
			info.Addresses = append(info.Addresses, a.String())
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
220
		}
221
		sort.Strings(info.Addresses)
222
		info.Protocols = node.PeerHost.Mux().Protocols()
223
		sort.Strings(info.Protocols)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
224
	}
225
	info.ProtocolVersion = identify.LibP2PVersion
Steven Allen's avatar
Steven Allen committed
226
	info.AgentVersion = version.UserAgent
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
227 228
	return info, nil
}