swarm.go 8.45 KB
Newer Older
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
1 2 3 4
package commands

import (
	"bytes"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
5
	"errors"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
6
	"fmt"
7
	"io"
8
	"path"
9
	"sort"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
10

11 12 13
	cmds "github.com/ipfs/go-ipfs/commands"
	peer "github.com/ipfs/go-ipfs/p2p/peer"
	iaddr "github.com/ipfs/go-ipfs/util/ipfsaddr"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
14

15 16
	ma "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr"
	context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
17 18 19 20 21 22
)

type stringList struct {
	Strings []string
}

23 24 25 26
type addrMap struct {
	Addrs map[string][]string
}

27
var SwarmCmd = &cmds.Command{
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
28 29 30
	Helptext: cmds.HelpText{
		Tagline: "swarm inspection tool",
		Synopsis: `
31
ipfs swarm peers                - List peers with open connections
32
ipfs swarm addrs                - List known addresses. Useful to debug.
33 34
ipfs swarm connect <address>    - Open connection to a given address
ipfs swarm disconnect <address> - Close connection to a given address
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
35 36 37 38 39 40 41 42
`,
		ShortDescription: `
ipfs swarm is a tool to manipulate the network swarm. The swarm is the
component that opens, listens for, and maintains connections to other
ipfs peers in the internet.
`,
	},
	Subcommands: map[string]*cmds.Command{
43
		"peers":      swarmPeersCmd,
44
		"addrs":      swarmAddrsCmd,
45 46
		"connect":    swarmConnectCmd,
		"disconnect": swarmDisconnectCmd,
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
47 48 49 50 51 52 53 54 55 56
	},
}

var swarmPeersCmd = &cmds.Command{
	Helptext: cmds.HelpText{
		Tagline: "List peers with open connections",
		ShortDescription: `
ipfs swarm peers lists the set of peers this node is connected to.
`,
	},
57
	Run: func(req cmds.Request, res cmds.Response) {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
58 59 60 61

		log.Debug("ipfs swarm peers")
		n, err := req.Context().GetNode()
		if err != nil {
62 63
			res.SetError(err, cmds.ErrNormal)
			return
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
64 65
		}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
66
		if n.PeerHost == nil {
67 68
			res.SetError(errNotOnline, cmds.ErrClient)
			return
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
69 70
		}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
71
		conns := n.PeerHost.Network().Conns()
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
72 73
		addrs := make([]string, len(conns))
		for i, c := range conns {
74
			pid := c.RemotePeer()
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
75
			addr := c.RemoteMultiaddr()
76
			addrs[i] = fmt.Sprintf("%s/ipfs/%s", addr, pid.Pretty())
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
77 78
		}

79
		sort.Sort(sort.StringSlice(addrs))
80
		res.SetOutput(&stringList{addrs})
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
81 82 83 84
	},
	Marshalers: cmds.MarshalerMap{
		cmds.Text: stringListMarshaler,
	},
85
	Type: stringList{},
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
86 87
}

88 89 90 91 92 93 94
var swarmAddrsCmd = &cmds.Command{
	Helptext: cmds.HelpText{
		Tagline: "List known addresses. Useful to debug.",
		ShortDescription: `
ipfs swarm addrs lists all addresses this node is aware of.
`,
	},
95 96 97
	Subcommands: map[string]*cmds.Command{
		"local": swarmAddrsLocalCmd,
	},
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
	Run: func(req cmds.Request, res cmds.Response) {

		n, err := req.Context().GetNode()
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		if n.PeerHost == nil {
			res.SetError(errNotOnline, cmds.ErrClient)
			return
		}

		addrs := make(map[string][]string)
		ps := n.PeerHost.Network().Peerstore()
		for _, p := range ps.Peers() {
			s := p.Pretty()
			for _, a := range ps.Addrs(p) {
				addrs[s] = append(addrs[s], a.String())
			}
			sort.Sort(sort.StringSlice(addrs[s]))
		}

		res.SetOutput(&addrMap{Addrs: addrs})
	},
	Marshalers: cmds.MarshalerMap{
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
			m, ok := res.Output().(*addrMap)
			if !ok {
				return nil, errors.New("failed to cast map[string]string")
			}

			// sort the ids first
			ids := make([]string, 0, len(m.Addrs))
			for p := range m.Addrs {
				ids = append(ids, p)
			}
			sort.Sort(sort.StringSlice(ids))

137
			buf := new(bytes.Buffer)
138 139 140 141 142 143 144
			for _, p := range ids {
				paddrs := m.Addrs[p]
				buf.WriteString(fmt.Sprintf("%s (%d)\n", p, len(paddrs)))
				for _, addr := range paddrs {
					buf.WriteString("\t" + addr + "\n")
				}
			}
145
			return buf, nil
146 147 148 149 150
		},
	},
	Type: addrMap{},
}

151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
var swarmAddrsLocalCmd = &cmds.Command{
	Helptext: cmds.HelpText{
		Tagline: "List local addresses.",
		ShortDescription: `
ipfs swarm addrs local lists all local addresses the node is listening on.
`,
	},
	Options: []cmds.Option{
		cmds.BoolOption("id", "Show peer ID in addresses"),
	},
	Run: func(req cmds.Request, res cmds.Response) {

		n, err := req.Context().GetNode()
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		if n.PeerHost == nil {
			res.SetError(errNotOnline, cmds.ErrClient)
			return
		}

		showid, _, _ := req.Option("id").Bool()
		id := n.Identity.Pretty()

		var addrs []string
		for _, addr := range n.PeerHost.Addrs() {
			saddr := addr.String()
			if showid {
				saddr = path.Join(saddr, "ipfs", id)
			}
			addrs = append(addrs, saddr)
		}
		sort.Sort(sort.StringSlice(addrs))

		res.SetOutput(&stringList{addrs})
	},
	Type: stringList{},
	Marshalers: cmds.MarshalerMap{
		cmds.Text: stringListMarshaler,
	},
}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
195 196
var swarmConnectCmd = &cmds.Command{
	Helptext: cmds.HelpText{
197
		Tagline: "Open connection to a given address",
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
198
		ShortDescription: `
199 200 201
'ipfs swarm connect' opens a new direct connection to a peer address.

The address format is an ipfs multiaddr:
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
202

203
ipfs swarm connect /ip4/104.131.131.82/tcp/4001/ipfs/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
204 205 206
`,
	},
	Arguments: []cmds.Argument{
207
		cmds.StringArg("address", true, true, "address of peer to connect to").EnableStdin(),
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
208
	},
209
	Run: func(req cmds.Request, res cmds.Response) {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
210 211 212 213
		ctx := context.TODO()

		n, err := req.Context().GetNode()
		if err != nil {
214 215
			res.SetError(err, cmds.ErrNormal)
			return
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
216 217
		}

218
		addrs := req.Arguments()
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
219

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
220
		if n.PeerHost == nil {
221 222
			res.SetError(errNotOnline, cmds.ErrClient)
			return
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
223 224
		}

225
		pis, err := peersWithAddresses(addrs)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
226
		if err != nil {
227 228
			res.SetError(err, cmds.ErrNormal)
			return
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
229 230
		}

231 232 233
		output := make([]string, len(pis))
		for i, pi := range pis {
			output[i] = "connect " + pi.ID.Pretty()
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
234

235
			err := n.PeerHost.Connect(ctx, pi)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
236 237 238 239 240 241 242
			if err != nil {
				output[i] += " failure: " + err.Error()
			} else {
				output[i] += " success"
			}
		}

243
		res.SetOutput(&stringList{output})
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
244 245 246 247
	},
	Marshalers: cmds.MarshalerMap{
		cmds.Text: stringListMarshaler,
	},
248
	Type: stringList{},
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
249 250
}

251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292
var swarmDisconnectCmd = &cmds.Command{
	Helptext: cmds.HelpText{
		Tagline: "Close connection to a given address",
		ShortDescription: `
'ipfs swarm disconnect' closes a connection to a peer address. The address format
is an ipfs multiaddr:

ipfs swarm disconnect /ip4/104.131.131.82/tcp/4001/ipfs/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ
`,
	},
	Arguments: []cmds.Argument{
		cmds.StringArg("address", true, true, "address of peer to connect to").EnableStdin(),
	},
	Run: func(req cmds.Request, res cmds.Response) {
		n, err := req.Context().GetNode()
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		addrs := req.Arguments()

		if n.PeerHost == nil {
			res.SetError(errNotOnline, cmds.ErrClient)
			return
		}

		iaddrs, err := parseAddresses(addrs)
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		output := make([]string, len(iaddrs))
		for i, addr := range iaddrs {
			taddr := addr.Transport()
			output[i] = "disconnect " + addr.ID().Pretty()

			found := false
			conns := n.PeerHost.Network().ConnsToPeer(addr.ID())
			for _, conn := range conns {
				if !conn.RemoteMultiaddr().Equal(taddr) {
293
					log.Debug("it's not", conn.RemoteMultiaddr(), taddr)
294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317
					continue
				}

				if err := conn.Close(); err != nil {
					output[i] += " failure: " + err.Error()
				} else {
					output[i] += " success"
				}
				found = true
				break
			}

			if !found {
				output[i] += " failure: conn not found"
			}
		}
		res.SetOutput(&stringList{output})
	},
	Marshalers: cmds.MarshalerMap{
		cmds.Text: stringListMarshaler,
	},
	Type: stringList{},
}

318
func stringListMarshaler(res cmds.Response) (io.Reader, error) {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
319 320 321 322 323
	list, ok := res.Output().(*stringList)
	if !ok {
		return nil, errors.New("failed to cast []string")
	}

324
	buf := new(bytes.Buffer)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
325
	for _, s := range list.Strings {
326 327
		buf.WriteString(s)
		buf.WriteString("\n")
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
328
	}
329
	return buf, nil
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
330
}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
331

332
// parseAddresses is a function that takes in a slice of string peer addresses
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
333
// (multiaddr + peerid) and returns slices of multiaddrs and peerids.
334 335 336 337
func parseAddresses(addrs []string) (iaddrs []iaddr.IPFSAddr, err error) {
	iaddrs = make([]iaddr.IPFSAddr, len(addrs))
	for i, saddr := range addrs {
		iaddrs[i], err = iaddr.ParseString(saddr)
338
		if err != nil {
339
			return nil, cmds.ClientError("invalid peer address: " + err.Error())
340
		}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
341 342 343 344 345 346
	}
	return
}

// peersWithAddresses is a function that takes in a slice of string peer addresses
// (multiaddr + peerid) and returns a slice of properly constructed peers
347
func peersWithAddresses(addrs []string) (pis []peer.PeerInfo, err error) {
348
	iaddrs, err := parseAddresses(addrs)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
349 350 351 352
	if err != nil {
		return nil, err
	}

353
	for _, iaddr := range iaddrs {
354 355 356 357
		pis = append(pis, peer.PeerInfo{
			ID:    iaddr.ID(),
			Addrs: []ma.Multiaddr{iaddr.Transport()},
		})
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
358
	}
359
	return pis, nil
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
360
}