p2p.go 9.26 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
	ma "gx/ipfs/QmcyqRMCAXVtYPS4DiBrA7sezL9rRGfW8Ctx7cywL4TXJj/go-multiaddr"
15 16
)

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

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

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

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

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

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

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

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

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

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

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

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

96
		n, err := getNode(req)
97 98 99 100 101
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

Łukasz Magiera's avatar
Łukasz Magiera committed
102
		output := &P2PLsOutput{}
103

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

111 112
		res.SetOutput(output)
	},
Łukasz Magiera's avatar
Łukasz Magiera committed
113
	Type: P2PLsOutput{},
114 115 116
	Marshalers: cmds.MarshalerMap{
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
			headers, _, _ := res.Request().Option("headers").Bool()
Łukasz Magiera's avatar
Łukasz Magiera committed
117
			list, _ := res.Output().(*P2PLsOutput)
118 119
			buf := new(bytes.Buffer)
			w := tabwriter.NewWriter(buf, 1, 2, 1, ' ', 0)
120
			for _, listener := range list.Listeners {
121 122 123 124
				if headers {
					fmt.Fprintln(w, "Address\tProtocol")
				}

125
				fmt.Fprintf(w, "%s\t%s\n", listener.Address, listener.Protocol)
126 127 128 129 130 131 132 133
			}
			w.Flush()

			return buf, nil
		},
	},
}

Łukasz Magiera's avatar
Łukasz Magiera committed
134
var p2pStreamLsCmd = &cmds.Command{
135
	Helptext: cmds.HelpText{
136
		Tagline: "List active p2p streams.",
137 138
	},
	Options: []cmds.Option{
139
		cmds.BoolOption("headers", "v", "Print table headers (HagndlerID, Protocol, Local, Remote).").Default(false),
140 141
	},
	Run: func(req cmds.Request, res cmds.Response) {
142
		n, err := getNode(req)
143 144 145 146 147
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

Łukasz Magiera's avatar
Łukasz Magiera committed
148
		output := &P2PStreamsOutput{}
149

Łukasz Magiera's avatar
Łukasz Magiera committed
150 151
		for _, s := range n.P2P.Streams.Streams {
			output.Streams = append(output.Streams, P2PStreamInfoOutput{
152
				HandlerID: strconv.FormatUint(s.HandlerID, 10),
153

154
				Protocol: s.Protocol,
155

156 157
				LocalPeer:    s.LocalPeer.Pretty(),
				LocalAddress: s.LocalAddr.String(),
158

159 160
				RemotePeer:    s.RemotePeer.Pretty(),
				RemoteAddress: s.RemoteAddr.String(),
161 162 163
			})
		}

164 165
		res.SetOutput(output)
	},
Łukasz Magiera's avatar
Łukasz Magiera committed
166
	Type: P2PStreamsOutput{},
167 168 169
	Marshalers: cmds.MarshalerMap{
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
			headers, _, _ := res.Request().Option("headers").Bool()
Łukasz Magiera's avatar
Łukasz Magiera committed
170
			list, _ := res.Output().(*P2PStreamsOutput)
171 172 173 174
			buf := new(bytes.Buffer)
			w := tabwriter.NewWriter(buf, 1, 2, 1, ' ', 0)
			for _, stream := range list.Streams {
				if headers {
175
					fmt.Fprintln(w, "HandlerID\tProtocol\tLocal\tRemote")
176 177
				}

178
				fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", stream.HandlerID, stream.Protocol, stream.LocalAddress, stream.RemotePeer)
179 180 181 182 183
			}
			w.Flush()

			return buf, nil
		},
184 185 186
	},
}

Łukasz Magiera's avatar
Łukasz Magiera committed
187
var p2pListenerListenCmd = &cmds.Command{
188
	Helptext: cmds.HelpText{
189
		Tagline: "Forward p2p connections to a network multiaddr.",
190
		ShortDescription: `
191
Register a p2p connection handler and forward the connections to a specified address.
192 193 194

Note that the connections originate from the ipfs daemon process.
		`,
195 196 197 198 199 200
	},
	Arguments: []cmds.Argument{
		cmds.StringArg("Protocol", true, false, "Protocol identifier."),
		cmds.StringArg("Address", true, false, "Request handling application address."),
	},
	Run: func(req cmds.Request, res cmds.Response) {
201
		n, err := getNode(req)
202 203 204 205 206
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

Łukasz Magiera's avatar
Łukasz Magiera committed
207 208
		proto := "/p2p/" + req.Arguments()[0]
		if n.P2P.CheckProtoExists(proto) {
209
			res.SetError(errors.New("protocol handler already registered"), cmds.ErrNormal)
210 211 212 213 214 215 216 217 218
			return
		}

		addr, err := ma.NewMultiaddr(req.Arguments()[1])
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

Łukasz Magiera's avatar
Łukasz Magiera committed
219
		_, err = n.P2P.NewListener(n.Context(), proto, addr)
220 221 222 223 224 225
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		// Successful response.
Łukasz Magiera's avatar
Łukasz Magiera committed
226
		res.SetOutput(&P2PListenerInfoOutput{
227 228 229 230 231 232
			Protocol: proto,
			Address:  addr.String(),
		})
	},
}

Łukasz Magiera's avatar
Łukasz Magiera committed
233
var p2pStreamDialCmd = &cmds.Command{
234
	Helptext: cmds.HelpText{
235
		Tagline: "Dial to a p2p listener.",
236 237 238 239 240 241

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

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
242
transparently connect to a p2p service.
243
		`,
244 245 246 247
	},
	Arguments: []cmds.Argument{
		cmds.StringArg("Peer", true, false, "Remote peer to connect to"),
		cmds.StringArg("Protocol", true, false, "Protocol identifier."),
248
		cmds.StringArg("BindAddress", false, false, "Address to listen for connection/s (default: /ip4/127.0.0.1/tcp/0)."),
249 250
	},
	Run: func(req cmds.Request, res cmds.Response) {
251
		n, err := getNode(req)
252 253 254 255 256 257 258 259 260 261 262
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		addr, peer, err := ParsePeerParam(req.Arguments()[0])
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

Łukasz Magiera's avatar
Łukasz Magiera committed
263
		proto := "/p2p/" + req.Arguments()[1]
264 265 266 267 268 269 270 271 272 273

		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 {
				res.SetError(err, cmds.ErrNormal)
				return
			}
		}

Łukasz Magiera's avatar
Łukasz Magiera committed
274
		listenerInfo, err := n.P2P.Dial(n.Context(), addr, peer, proto, bindAddr)
275 276 277 278 279
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

Łukasz Magiera's avatar
Łukasz Magiera committed
280
		output := P2PListenerInfoOutput{
281 282
			Protocol: listenerInfo.Protocol,
			Address:  listenerInfo.Address.String(),
283 284 285 286 287 288
		}

		res.SetOutput(&output)
	},
}

Łukasz Magiera's avatar
Łukasz Magiera committed
289
var p2pListenerCloseCmd = &cmds.Command{
290
	Helptext: cmds.HelpText{
291
		Tagline: "Close active p2p listener.",
292 293
	},
	Arguments: []cmds.Argument{
294
		cmds.StringArg("Protocol", false, false, "P2P listener protocol"),
295 296
	},
	Options: []cmds.Option{
297
		cmds.BoolOption("all", "a", "Close all listeners.").Default(false),
298 299
	},
	Run: func(req cmds.Request, res cmds.Response) {
300
		n, err := getNode(req)
301 302 303 304 305
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

306 307 308 309 310 311 312 313 314
		closeAll, _, _ := req.Option("all").Bool()
		var proto string

		if !closeAll {
			if len(req.Arguments()) == 0 {
				res.SetError(errors.New("no protocol name specified"), cmds.ErrNormal)
				return
			}

Łukasz Magiera's avatar
Łukasz Magiera committed
315
			proto = "/p2p/" + req.Arguments()[0]
316 317
		}

Łukasz Magiera's avatar
Łukasz Magiera committed
318
		for _, listener := range n.P2P.Listeners.Listeners {
319 320 321 322 323 324 325 326 327 328 329
			if !closeAll && listener.Protocol != proto {
				continue
			}
			listener.Close()
			if !closeAll {
				break
			}
		}
	},
}

Łukasz Magiera's avatar
Łukasz Magiera committed
330
var p2pStreamCloseCmd = &cmds.Command{
331 332 333 334 335 336 337 338 339 340 341 342 343
	Helptext: cmds.HelpText{
		Tagline: "Close active p2p stream.",
	},
	Arguments: []cmds.Argument{
		cmds.StringArg("HandlerID", false, false, "Stream HandlerID"),
	},
	Options: []cmds.Option{
		cmds.BoolOption("all", "a", "Close all streams.").Default(false),
	},
	Run: func(req cmds.Request, res cmds.Response) {
		n, err := getNode(req)
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
344 345 346 347
			return
		}

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

350 351
		if !closeAll {
			if len(req.Arguments()) == 0 {
352
				res.SetError(errors.New("no HandlerID specified"), cmds.ErrNormal)
353 354
				return
			}
355

356
			handlerID, err = strconv.ParseUint(req.Arguments()[0], 10, 64)
357
			if err != nil {
358 359
				res.SetError(err, cmds.ErrNormal)
				return
360 361 362
			}
		}

Łukasz Magiera's avatar
Łukasz Magiera committed
363
		for _, stream := range n.P2P.Streams.Streams {
364 365
			if !closeAll && handlerID != stream.HandlerID {
				continue
366
			}
367 368 369
			stream.Close()
			if !closeAll {
				break
370 371 372 373
			}
		}
	},
}
374

375 376 377 378 379 380
func getNode(req cmds.Request) (*core.IpfsNode, error) {
	n, err := req.InvocContext().GetNode()
	if err != nil {
		return nil, err
	}

381 382
	config, err := n.Repo.Config()
	if err != nil {
383
		return nil, err
384 385 386
	}

	if !config.Experimental.Libp2pStreamMounting {
387
		return nil, errors.New("libp2p stream mounting not enabled")
388
	}
389 390 391 392 393 394

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

	return n, nil
395
}