id.go 5.53 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"
Raúl Kripalani's avatar
Raúl Kripalani committed
17
	ic "github.com/libp2p/go-libp2p-core/crypto"
Steven Allen's avatar
Steven Allen committed
18
	"github.com/libp2p/go-libp2p-core/host"
Raúl Kripalani's avatar
Raúl Kripalani committed
19 20
	peer "github.com/libp2p/go-libp2p-core/peer"
	pstore "github.com/libp2p/go-libp2p-core/peerstore"
Jakub Sztandera's avatar
Jakub Sztandera committed
21 22
	kb "github.com/libp2p/go-libp2p-kbucket"
	identify "github.com/libp2p/go-libp2p/p2p/protocol/identify"
23 24
)

25 26 27 28 29 30 31 32 33
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
`

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

Kejie Zhang's avatar
Kejie Zhang committed
43 44 45 46
const (
	formatOptionName = "format"
)

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

Richard Littauer's avatar
Richard Littauer committed
54 55 56 57 58 59
'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).
60 61 62 63

EXAMPLE:

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

Jeromy's avatar
Jeromy committed
78
		var id peer.ID
79
		if len(req.Arguments) > 0 {
Steven Allen's avatar
Steven Allen committed
80
			var err error
81
			id, err = peer.Decode(req.Arguments[0])
Steven Allen's avatar
Steven Allen committed
82
			if err != nil {
83
				return fmt.Errorf("invalid peer id")
Jeromy's avatar
Jeromy committed
84 85
			}
		} else {
86
			id = n.Identity
Jeromy's avatar
Jeromy committed
87 88
		}

89 90
		if id == n.Identity {
			output, err := printSelf(n)
91
			if err != nil {
92
				return err
93
			}
94
			return cmds.EmitOnce(res, output)
95 96
		}

97
		// TODO handle offline mode with polymorphism instead of conditionals
98
		if !n.IsOnline {
99
			return errors.New(offlineIdErrorMessage)
100 101
		}

102 103 104 105 106
		// 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:
107
			return errors.New(offlineIdErrorMessage)
108
		default:
109
			return err
110
		}
111

112
		output, err := printPeer(n.Peerstore, id)
113
		if err != nil {
114
			return err
115
		}
116
		return cmds.EmitOnce(res, output)
117
	},
118
	Encoders: cmds.EncoderMap{
119
		cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, out *IdOutput) error {
120
			format, found := req.Options[formatOptionName].(string)
121 122
			if found {
				output := format
123 124 125 126 127
				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)
128
				output = strings.Replace(output, "<protocols>", strings.Join(out.Protocols, "\n"), -1)
129 130
				output = strings.Replace(output, "\\n", "\n", -1)
				output = strings.Replace(output, "\\t", "\t", -1)
131
				fmt.Fprint(w, output)
132
			} else {
133
				marshaled, err := json.MarshalIndent(out, "", "\t")
134
				if err != nil {
135
					return err
136
				}
137
				marshaled = append(marshaled, byte('\n'))
138
				fmt.Fprintln(w, string(marshaled))
139
			}
140 141
			return nil
		}),
142
	},
143
	Type: IdOutput{},
144 145
}

Jeromy's avatar
Jeromy committed
146
func printPeer(ps pstore.Peerstore, p peer.ID) (interface{}, error) {
147
	if p == "" {
Łukasz Magiera's avatar
Łukasz Magiera committed
148
		return nil, errors.New("attempted to print nil peer")
149
	}
150

151
	info := new(IdOutput)
152
	info.ID = p.Pretty()
153

154 155
	if pk := ps.PubKey(p); pk != nil {
		pkb, err := ic.MarshalPublicKey(pk)
Jeromy's avatar
Jeromy committed
156 157 158 159
		if err != nil {
			return nil, err
		}
		info.PublicKey = base64.StdEncoding.EncodeToString(pkb)
160
	}
161

162 163 164 165 166 167 168
	addrInfo := ps.PeerInfo(p)
	addrs, err := peer.AddrInfoToP2pAddrs(&addrInfo)
	if err != nil {
		return nil, err
	}

	for _, a := range addrs {
169 170 171
		info.Addresses = append(info.Addresses, a.String())
	}

172 173 174 175
	protocols, _ := ps.GetProtocols(p) // don't care about errors here.
	for _, p := range protocols {
		info.Protocols = append(info.Protocols, string(p))
	}
176
	sort.Strings(info.Protocols)
177

178 179
	if v, err := ps.Get(p, "ProtocolVersion"); err == nil {
		if vs, ok := v.(string); ok {
180
			info.ProtocolVersion = vs
181 182 183 184
		}
	}
	if v, err := ps.Get(p, "AgentVersion"); err == nil {
		if vs, ok := v.(string); ok {
185
			info.AgentVersion = vs
186 187
		}
	}
188 189 190

	return info, nil
}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
191 192 193 194 195 196 197 198 199 200 201 202 203 204

// printing self is special cased as we get values differently.
func printSelf(node *core.IpfsNode) (interface{}, error) {
	info := new(IdOutput)
	info.ID = node.Identity.Pretty()

	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
205 206 207 208 209 210
		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
211
		}
212
		info.Protocols = node.PeerHost.Mux().Protocols()
213
		sort.Strings(info.Protocols)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
214
	}
215
	info.ProtocolVersion = identify.LibP2PVersion
Steven Allen's avatar
Steven Allen committed
216
	info.AgentVersion = version.UserAgent
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
217 218
	return info, nil
}