id.go 5.59 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
		info.Addresses = append(info.Addresses, a.String())
	}
171
	sort.Strings(info.Addresses)
172

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

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

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

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