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

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

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

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

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

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

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

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

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

EXAMPLE:

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

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

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

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

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

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

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

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

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

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

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

171 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 177
	if v, err := ps.Get(p, "ProtocolVersion"); err == nil {
		if vs, ok := v.(string); ok {
178
			info.ProtocolVersion = vs
179 180 181 182
		}
	}
	if v, err := ps.Get(p, "AgentVersion"); err == nil {
		if vs, ok := v.(string); ok {
183
			info.AgentVersion = vs
184 185
		}
	}
186 187 188

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

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