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

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

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

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

type stringList struct {
	Strings []string
}

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

26
var SwarmCmd = &cmds.Command{
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
27 28 29
	Helptext: cmds.HelpText{
		Tagline: "swarm inspection tool",
		Synopsis: `
30
ipfs swarm peers                - List peers with open connections
31
ipfs swarm addrs                - List known addresses. Useful to debug.
32 33
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
34 35 36 37 38 39 40 41
`,
		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{
42
		"peers":      swarmPeersCmd,
43
		"addrs":      swarmAddrsCmd,
44 45
		"connect":    swarmConnectCmd,
		"disconnect": swarmDisconnectCmd,
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
46 47 48 49 50 51 52 53 54 55
	},
}

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.
`,
	},
56
	Run: func(req cmds.Request, res cmds.Response) {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
57 58 59 60

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

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

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

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

87 88 89 90 91 92 93 94 95 96 97 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 137 138 139 140 141 142 143 144 145 146
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.
`,
	},
	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))

			var buf bytes.Buffer
			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")
				}
			}
			return &buf, nil
		},
	},
	Type: addrMap{},
}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
147 148
var swarmConnectCmd = &cmds.Command{
	Helptext: cmds.HelpText{
149
		Tagline: "Open connection to a given address",
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
150 151 152 153
		ShortDescription: `
'ipfs swarm connect' opens a connection to a peer address. The address format
is an ipfs multiaddr:

154
ipfs swarm connect /ip4/104.131.131.82/tcp/4001/ipfs/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
155 156 157
`,
	},
	Arguments: []cmds.Argument{
158
		cmds.StringArg("address", true, true, "address of peer to connect to").EnableStdin(),
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
159
	},
160
	Run: func(req cmds.Request, res cmds.Response) {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
161 162 163 164
		ctx := context.TODO()

		n, err := req.Context().GetNode()
		if err != nil {
165 166
			res.SetError(err, cmds.ErrNormal)
			return
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
167 168
		}

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

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
171
		if n.PeerHost == nil {
172 173
			res.SetError(errNotOnline, cmds.ErrClient)
			return
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
174 175
		}

176
		pis, err := peersWithAddresses(addrs)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
177
		if err != nil {
178 179
			res.SetError(err, cmds.ErrNormal)
			return
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
180 181
		}

182 183 184
		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
185

186
			err := n.PeerHost.Connect(ctx, pi)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
187 188 189 190 191 192 193
			if err != nil {
				output[i] += " failure: " + err.Error()
			} else {
				output[i] += " success"
			}
		}

194
		res.SetOutput(&stringList{output})
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
195 196 197 198
	},
	Marshalers: cmds.MarshalerMap{
		cmds.Text: stringListMarshaler,
	},
199
	Type: stringList{},
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
200 201
}

202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243
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) {
244
					log.Debug("it's not", conn.RemoteMultiaddr(), taddr)
245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268
					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{},
}

269
func stringListMarshaler(res cmds.Response) (io.Reader, error) {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
270 271 272 273 274 275 276
	list, ok := res.Output().(*stringList)
	if !ok {
		return nil, errors.New("failed to cast []string")
	}

	var buf bytes.Buffer
	for _, s := range list.Strings {
277 278
		buf.WriteString(s)
		buf.WriteString("\n")
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
279
	}
280
	return &buf, nil
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
281
}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
282

283
// parseAddresses is a function that takes in a slice of string peer addresses
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
284
// (multiaddr + peerid) and returns slices of multiaddrs and peerids.
285 286 287 288
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)
289
		if err != nil {
290
			return nil, cmds.ClientError("invalid peer address: " + err.Error())
291
		}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
292 293 294 295 296 297
	}
	return
}

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

304
	for _, iaddr := range iaddrs {
305 306 307 308
		pis = append(pis, peer.PeerInfo{
			ID:    iaddr.ID(),
			Addrs: []ma.Multiaddr{iaddr.Transport()},
		})
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
309
	}
310
	return pis, nil
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
311
}