p2p.go 9.64 KB
Newer Older
1 2 3
package commands

import (
4
	"bytes"
5
	"errors"
6
	"fmt"
7 8
	"io"
	"strconv"
9
	"text/tabwriter"
10 11

	cmds "github.com/ipfs/go-ipfs/commands"
12
	core "github.com/ipfs/go-ipfs/core"
13

14
	pstore "gx/ipfs/QmXauCuJzmzapetmC6W4TuDJLL1yFFrVzSHoWv8YdbmnxH/go-libp2p-peerstore"
Steven Allen's avatar
Steven Allen committed
15
	ma "gx/ipfs/QmYmsdtJ3HsodkePE3eU3TsCaP2YvPZJ4LoXnNkDE5Tpt7/go-multiaddr"
16
	"gx/ipfs/QmdE4gMduCKCGAcczM2F5ioYDfdeKuPix138wrES1YSr7f/go-ipfs-cmdkit"
17 18
)

Łukasz Magiera's avatar
Łukasz Magiera committed
19 20
// P2PListenerInfoOutput is output type of ls command
type P2PListenerInfoOutput struct {
21 22 23 24
	Protocol string
	Address  string
}

Łukasz Magiera's avatar
Łukasz Magiera committed
25 26
// P2PStreamInfoOutput is output type of streams command
type P2PStreamInfoOutput struct {
27
	HandlerID     string
28 29 30 31 32 33 34
	Protocol      string
	LocalPeer     string
	LocalAddress  string
	RemotePeer    string
	RemoteAddress string
}

Łukasz Magiera's avatar
Łukasz Magiera committed
35 36 37
// P2PLsOutput is output type of ls command
type P2PLsOutput struct {
	Listeners []P2PListenerInfoOutput
38 39
}

Łukasz Magiera's avatar
Łukasz Magiera committed
40 41 42
// P2PStreamsOutput is output type of streams command
type P2PStreamsOutput struct {
	Streams []P2PStreamInfoOutput
43 44
}

Łukasz Magiera's avatar
Łukasz Magiera committed
45
// P2PCmd is the 'ipfs p2p' command
Łukasz Magiera's avatar
Łukasz Magiera committed
46
var P2PCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
47
	Helptext: cmdkit.HelpText{
48 49
		Tagline: "Libp2p stream mounting.",
		ShortDescription: `
50
Create and use tunnels to remote peers over libp2p
51

52 53
Note: this command is experimental and subject to change as usecases and APIs
are refined`,
54 55 56
	},

	Subcommands: map[string]*cmds.Command{
Łukasz Magiera's avatar
Łukasz Magiera committed
57 58
		"listener": p2pListenerCmd,
		"stream":   p2pStreamCmd,
59 60 61
	},
}

Łukasz Magiera's avatar
Łukasz Magiera committed
62 63
// p2pListenerCmd is the 'ipfs p2p listener' command
var p2pListenerCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
64
	Helptext: cmdkit.HelpText{
65 66 67 68 69
		Tagline:          "P2P listener management.",
		ShortDescription: "Create and manage listener p2p endpoints",
	},

	Subcommands: map[string]*cmds.Command{
Łukasz Magiera's avatar
Łukasz Magiera committed
70 71 72
		"ls":    p2pListenerLsCmd,
		"open":  p2pListenerListenCmd,
		"close": p2pListenerCloseCmd,
73 74 75
	},
}

Łukasz Magiera's avatar
Łukasz Magiera committed
76 77
// p2pStreamCmd is the 'ipfs p2p stream' command
var p2pStreamCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
78
	Helptext: cmdkit.HelpText{
79 80 81 82 83
		Tagline:          "P2P stream management.",
		ShortDescription: "Create and manage p2p streams",
	},

	Subcommands: map[string]*cmds.Command{
Łukasz Magiera's avatar
Łukasz Magiera committed
84 85 86
		"ls":    p2pStreamLsCmd,
		"dial":  p2pStreamDialCmd,
		"close": p2pStreamCloseCmd,
87 88 89
	},
}

Łukasz Magiera's avatar
Łukasz Magiera committed
90
var p2pListenerLsCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
91
	Helptext: cmdkit.HelpText{
92
		Tagline: "List active p2p listeners.",
93
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
94
	Options: []cmdkit.Option{
95
		cmdkit.BoolOption("headers", "v", "Print table headers (Id, Protocol, Local, Remote)."),
96 97 98
	},
	Run: func(req cmds.Request, res cmds.Response) {

99
		n, err := getNode(req)
100
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
101
			res.SetError(err, cmdkit.ErrNormal)
102 103 104
			return
		}

Łukasz Magiera's avatar
Łukasz Magiera committed
105
		output := &P2PLsOutput{}
106

Łukasz Magiera's avatar
Łukasz Magiera committed
107 108
		for _, listener := range n.P2P.Listeners.Listeners {
			output.Listeners = append(output.Listeners, P2PListenerInfoOutput{
109 110
				Protocol: listener.Protocol(),
				Address:  listener.Address(),
111 112 113
			})
		}

114 115
		res.SetOutput(output)
	},
Łukasz Magiera's avatar
Łukasz Magiera committed
116
	Type: P2PLsOutput{},
117 118
	Marshalers: cmds.MarshalerMap{
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
Jan Winkelmann's avatar
Jan Winkelmann committed
119 120 121 122 123
			v, err := unwrapOutput(res.Output())
			if err != nil {
				return nil, err
			}

124
			headers, _, _ := res.Request().Option("headers").Bool()
Jan Winkelmann's avatar
Jan Winkelmann committed
125
			list := v.(*P2PLsOutput)
126 127
			buf := new(bytes.Buffer)
			w := tabwriter.NewWriter(buf, 1, 2, 1, ' ', 0)
128
			for _, listener := range list.Listeners {
129 130 131 132
				if headers {
					fmt.Fprintln(w, "Address\tProtocol")
				}

133
				fmt.Fprintf(w, "%s\t%s\n", listener.Address, listener.Protocol)
134 135 136 137 138 139 140 141
			}
			w.Flush()

			return buf, nil
		},
	},
}

Łukasz Magiera's avatar
Łukasz Magiera committed
142
var p2pStreamLsCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
143
	Helptext: cmdkit.HelpText{
144
		Tagline: "List active p2p streams.",
145
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
146
	Options: []cmdkit.Option{
147
		cmdkit.BoolOption("headers", "v", "Print table headers (HagndlerID, Protocol, Local, Remote)."),
148 149
	},
	Run: func(req cmds.Request, res cmds.Response) {
150
		n, err := getNode(req)
151
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
152
			res.SetError(err, cmdkit.ErrNormal)
153 154 155
			return
		}

Łukasz Magiera's avatar
Łukasz Magiera committed
156
		output := &P2PStreamsOutput{}
157

Łukasz Magiera's avatar
Łukasz Magiera committed
158 159
		for _, s := range n.P2P.Streams.Streams {
			output.Streams = append(output.Streams, P2PStreamInfoOutput{
160
				HandlerID: strconv.FormatUint(s.Id, 10),
161

162
				Protocol: s.Protocol,
163

164 165
				LocalPeer:    s.LocalPeer.Pretty(),
				LocalAddress: s.LocalAddr.String(),
166

167 168
				RemotePeer:    s.RemotePeer.Pretty(),
				RemoteAddress: s.RemoteAddr.String(),
169 170 171
			})
		}

172 173
		res.SetOutput(output)
	},
Łukasz Magiera's avatar
Łukasz Magiera committed
174
	Type: P2PStreamsOutput{},
175 176
	Marshalers: cmds.MarshalerMap{
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
Jan Winkelmann's avatar
Jan Winkelmann committed
177 178 179 180 181
			v, err := unwrapOutput(res.Output())
			if err != nil {
				return nil, err
			}

182
			headers, _, _ := res.Request().Option("headers").Bool()
Jan Winkelmann's avatar
Jan Winkelmann committed
183
			list := v.(*P2PStreamsOutput)
184 185 186 187
			buf := new(bytes.Buffer)
			w := tabwriter.NewWriter(buf, 1, 2, 1, ' ', 0)
			for _, stream := range list.Streams {
				if headers {
188
					fmt.Fprintln(w, "Id\tProtocol\tLocal\tRemote")
189 190
				}

191
				fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", stream.HandlerID, stream.Protocol, stream.LocalAddress, stream.RemotePeer)
192 193 194 195 196
			}
			w.Flush()

			return buf, nil
		},
197 198 199
	},
}

Łukasz Magiera's avatar
Łukasz Magiera committed
200
var p2pListenerListenCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
201
	Helptext: cmdkit.HelpText{
202
		Tagline: "Forward p2p connections to a network multiaddr.",
203
		ShortDescription: `
204 205
Register a p2p connection handler and forward the connections to a specified
address.
206 207 208

Note that the connections originate from the ipfs daemon process.
		`,
209
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
210 211 212
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("Protocol", true, false, "Protocol identifier."),
		cmdkit.StringArg("Address", true, false, "Request handling application address."),
213 214
	},
	Run: func(req cmds.Request, res cmds.Response) {
215
		n, err := getNode(req)
216
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
217
			res.SetError(err, cmdkit.ErrNormal)
218 219 220
			return
		}

Łukasz Magiera's avatar
Łukasz Magiera committed
221 222
		proto := "/p2p/" + req.Arguments()[0]
		if n.P2P.CheckProtoExists(proto) {
Jan Winkelmann's avatar
Jan Winkelmann committed
223
			res.SetError(errors.New("protocol handler already registered"), cmdkit.ErrNormal)
224 225 226 227 228
			return
		}

		addr, err := ma.NewMultiaddr(req.Arguments()[1])
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
229
			res.SetError(err, cmdkit.ErrNormal)
230 231 232
			return
		}

Łukasz Magiera's avatar
Łukasz Magiera committed
233
		_, err = n.P2P.NewListener(n.Context(), proto, addr)
234
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
235
			res.SetError(err, cmdkit.ErrNormal)
236 237 238 239
			return
		}

		// Successful response.
Łukasz Magiera's avatar
Łukasz Magiera committed
240
		res.SetOutput(&P2PListenerInfoOutput{
241 242 243 244 245 246
			Protocol: proto,
			Address:  addr.String(),
		})
	},
}

Łukasz Magiera's avatar
Łukasz Magiera committed
247
var p2pStreamDialCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
248
	Helptext: cmdkit.HelpText{
249
		Tagline: "Dial to a p2p listener.",
250 251 252 253

		ShortDescription: `
Establish a new connection to a peer service.

254 255 256
When a connection is made to a peer service the ipfs daemon will setup one
time TCP listener and return it's bind port, this way a dialing application
can transparently connect to a p2p service.
257
		`,
258
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
259 260 261 262
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("Peer", true, false, "Remote peer to connect to"),
		cmdkit.StringArg("Protocol", true, false, "Protocol identifier."),
		cmdkit.StringArg("BindAddress", false, false, "Address to listen for connection/s (default: /ip4/127.0.0.1/tcp/0)."),
263 264
	},
	Run: func(req cmds.Request, res cmds.Response) {
265
		n, err := getNode(req)
266
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
267
			res.SetError(err, cmdkit.ErrNormal)
268 269 270
			return
		}

271
		addr, peer, err := ParsePeerParam(req.Arguments()[0])
272
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
273
			res.SetError(err, cmdkit.ErrNormal)
274 275 276
			return
		}

277 278 279 280
		if addr != nil {
			n.Peerstore.AddAddr(peer, addr, pstore.TempAddrTTL)
		}

Łukasz Magiera's avatar
Łukasz Magiera committed
281
		proto := "/p2p/" + req.Arguments()[1]
282 283 284 285 286

		bindAddr, _ := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/0")
		if len(req.Arguments()) == 3 {
			bindAddr, err = ma.NewMultiaddr(req.Arguments()[2])
			if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
287
				res.SetError(err, cmdkit.ErrNormal)
288 289 290 291
				return
			}
		}

292
		listenerInfo, err := n.P2P.Dial(n.Context(), peer, proto, bindAddr)
293
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
294
			res.SetError(err, cmdkit.ErrNormal)
295 296 297
			return
		}

Łukasz Magiera's avatar
Łukasz Magiera committed
298
		output := P2PListenerInfoOutput{
299 300
			Protocol: listenerInfo.Protocol(),
			Address:  listenerInfo.Address(),
301 302 303 304 305 306
		}

		res.SetOutput(&output)
	},
}

Łukasz Magiera's avatar
Łukasz Magiera committed
307
var p2pListenerCloseCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
308
	Helptext: cmdkit.HelpText{
309
		Tagline: "Close active p2p listener.",
310
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
311 312
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("Protocol", false, false, "P2P listener protocol"),
313
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
314
	Options: []cmdkit.Option{
315
		cmdkit.BoolOption("all", "a", "Close all listeners."),
316 317
	},
	Run: func(req cmds.Request, res cmds.Response) {
Jan Winkelmann's avatar
Jan Winkelmann committed
318 319
		res.SetOutput(nil)

320
		n, err := getNode(req)
321
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
322
			res.SetError(err, cmdkit.ErrNormal)
323 324 325
			return
		}

326 327 328 329 330
		closeAll, _, _ := req.Option("all").Bool()
		var proto string

		if !closeAll {
			if len(req.Arguments()) == 0 {
Jan Winkelmann's avatar
Jan Winkelmann committed
331
				res.SetError(errors.New("no protocol name specified"), cmdkit.ErrNormal)
332 333 334
				return
			}

Łukasz Magiera's avatar
Łukasz Magiera committed
335
			proto = "/p2p/" + req.Arguments()[0]
336 337
		}

Łukasz Magiera's avatar
Łukasz Magiera committed
338
		for _, listener := range n.P2P.Listeners.Listeners {
339
			if !closeAll && listener.Protocol() != proto {
340 341 342 343 344 345 346 347 348 349
				continue
			}
			listener.Close()
			if !closeAll {
				break
			}
		}
	},
}

Łukasz Magiera's avatar
Łukasz Magiera committed
350
var p2pStreamCloseCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
351
	Helptext: cmdkit.HelpText{
352 353
		Tagline: "Close active p2p stream.",
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
354
	Arguments: []cmdkit.Argument{
355
		cmdkit.StringArg("Id", false, false, "Stream Id"),
356
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
357
	Options: []cmdkit.Option{
358
		cmdkit.BoolOption("all", "a", "Close all streams."),
359 360
	},
	Run: func(req cmds.Request, res cmds.Response) {
Jan Winkelmann's avatar
Jan Winkelmann committed
361 362
		res.SetOutput(nil)

363 364
		n, err := getNode(req)
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
365
			res.SetError(err, cmdkit.ErrNormal)
366 367 368 369
			return
		}

		closeAll, _, _ := req.Option("all").Bool()
370
		var handlerID uint64
371

372 373
		if !closeAll {
			if len(req.Arguments()) == 0 {
374
				res.SetError(errors.New("no Id specified"), cmdkit.ErrNormal)
375 376
				return
			}
377

378
			handlerID, err = strconv.ParseUint(req.Arguments()[0], 10, 64)
379
			if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
380
				res.SetError(err, cmdkit.ErrNormal)
381
				return
382 383 384
			}
		}

Łukasz Magiera's avatar
Łukasz Magiera committed
385
		for _, stream := range n.P2P.Streams.Streams {
386
			if !closeAll && handlerID != stream.Id {
387
				continue
388
			}
389 390 391
			stream.Close()
			if !closeAll {
				break
392 393 394 395
			}
		}
	},
}
396

397 398 399 400 401 402
func getNode(req cmds.Request) (*core.IpfsNode, error) {
	n, err := req.InvocContext().GetNode()
	if err != nil {
		return nil, err
	}

403 404
	config, err := n.Repo.Config()
	if err != nil {
405
		return nil, err
406 407 408
	}

	if !config.Experimental.Libp2pStreamMounting {
409
		return nil, errors.New("libp2p stream mounting not enabled")
410
	}
411 412

	if !n.OnlineMode() {
413
		return nil, ErrNotOnline
414 415 416
	}

	return n, nil
417
}