p2p.go 9.54 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
	"gx/ipfs/QmSP88ryZkHSRn1fnngAaV2Vcn63WUJzAavnRM9CVdU1Ky/go-ipfs-cmdkit"
Steven Allen's avatar
Steven Allen committed
15
	ma "gx/ipfs/QmYmsdtJ3HsodkePE3eU3TsCaP2YvPZJ4LoXnNkDE5Tpt7/go-multiaddr"
16 17
)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

			return buf, nil
		},
	},
}

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

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

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

161
				Protocol: s.Protocol,
162

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

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

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

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

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

			return buf, nil
		},
196 197 198
	},
}

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

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

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

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

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

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

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

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

253 254 255
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.
256
		`,
257
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
258 259 260 261
	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)."),
262 263
	},
	Run: func(req cmds.Request, res cmds.Response) {
264
		n, err := getNode(req)
265
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
266
			res.SetError(err, cmdkit.ErrNormal)
267 268 269 270 271
			return
		}

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

Łukasz Magiera's avatar
Łukasz Magiera committed
276
		proto := "/p2p/" + req.Arguments()[1]
277 278 279 280 281

		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
282
				res.SetError(err, cmdkit.ErrNormal)
283 284 285 286
				return
			}
		}

Łukasz Magiera's avatar
Łukasz Magiera committed
287
		listenerInfo, err := n.P2P.Dial(n.Context(), addr, peer, proto, bindAddr)
288
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
289
			res.SetError(err, cmdkit.ErrNormal)
290 291 292
			return
		}

Łukasz Magiera's avatar
Łukasz Magiera committed
293
		output := P2PListenerInfoOutput{
294 295
			Protocol: listenerInfo.Protocol,
			Address:  listenerInfo.Address.String(),
296 297 298 299 300 301
		}

		res.SetOutput(&output)
	},
}

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

315
		n, err := getNode(req)
316
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
317
			res.SetError(err, cmdkit.ErrNormal)
318 319 320
			return
		}

321 322 323 324 325
		closeAll, _, _ := req.Option("all").Bool()
		var proto string

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

Łukasz Magiera's avatar
Łukasz Magiera committed
330
			proto = "/p2p/" + req.Arguments()[0]
331 332
		}

Łukasz Magiera's avatar
Łukasz Magiera committed
333
		for _, listener := range n.P2P.Listeners.Listeners {
334 335 336 337 338 339 340 341 342 343 344
			if !closeAll && listener.Protocol != proto {
				continue
			}
			listener.Close()
			if !closeAll {
				break
			}
		}
	},
}

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

358 359
		n, err := getNode(req)
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
360
			res.SetError(err, cmdkit.ErrNormal)
361 362 363 364
			return
		}

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

367 368
		if !closeAll {
			if len(req.Arguments()) == 0 {
Jan Winkelmann's avatar
Jan Winkelmann committed
369
				res.SetError(errors.New("no HandlerID specified"), cmdkit.ErrNormal)
370 371
				return
			}
372

373
			handlerID, err = strconv.ParseUint(req.Arguments()[0], 10, 64)
374
			if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
375
				res.SetError(err, cmdkit.ErrNormal)
376
				return
377 378 379
			}
		}

Łukasz Magiera's avatar
Łukasz Magiera committed
380
		for _, stream := range n.P2P.Streams.Streams {
381 382
			if !closeAll && handlerID != stream.HandlerID {
				continue
383
			}
384 385 386
			stream.Close()
			if !closeAll {
				break
387 388 389 390
			}
		}
	},
}
391

392 393 394 395 396 397
func getNode(req cmds.Request) (*core.IpfsNode, error) {
	n, err := req.InvocContext().GetNode()
	if err != nil {
		return nil, err
	}

398 399
	config, err := n.Repo.Config()
	if err != nil {
400
		return nil, err
401 402 403
	}

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

	if !n.OnlineMode() {
		return nil, errNotOnline
	}

	return n, nil
412
}