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/QmXY77cVe7rVRQXZZQRioukUM7aRW3BTcAgJe12MCtb3Ji/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 51
Note: this command is experimental and subject to change as usecases and APIs
are refined`,
52 53 54
	},

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

Łukasz Magiera's avatar
Łukasz Magiera committed
60 61
// p2pListenerCmd is the 'ipfs p2p listener' command
var p2pListenerCmd = &cmds.Command{
62 63 64 65 66 67
	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
68 69 70
		"ls":    p2pListenerLsCmd,
		"open":  p2pListenerListenCmd,
		"close": p2pListenerCloseCmd,
71 72 73
	},
}

Łukasz Magiera's avatar
Łukasz Magiera committed
74 75
// p2pStreamCmd is the 'ipfs p2p stream' command
var p2pStreamCmd = &cmds.Command{
76 77 78 79 80 81
	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
82 83 84
		"ls":    p2pStreamLsCmd,
		"dial":  p2pStreamDialCmd,
		"close": p2pStreamCloseCmd,
85 86 87
	},
}

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

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

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

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

112 113
		res.SetOutput(output)
	},
Łukasz Magiera's avatar
Łukasz Magiera committed
114
	Type: P2PLsOutput{},
115 116 117
	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
118
			list, _ := res.Output().(*P2PLsOutput)
119 120
			buf := new(bytes.Buffer)
			w := tabwriter.NewWriter(buf, 1, 2, 1, ' ', 0)
121
			for _, listener := range list.Listeners {
122 123 124 125
				if headers {
					fmt.Fprintln(w, "Address\tProtocol")
				}

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

			return buf, nil
		},
	},
}

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

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

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

155
				Protocol: s.Protocol,
156

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

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

165 166
		res.SetOutput(output)
	},
Łukasz Magiera's avatar
Łukasz Magiera committed
167
	Type: P2PStreamsOutput{},
168 169 170
	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
171
			list, _ := res.Output().(*P2PStreamsOutput)
172 173 174 175
			buf := new(bytes.Buffer)
			w := tabwriter.NewWriter(buf, 1, 2, 1, ' ', 0)
			for _, stream := range list.Streams {
				if headers {
176
					fmt.Fprintln(w, "HandlerID\tProtocol\tLocal\tRemote")
177 178
				}

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

			return buf, nil
		},
185 186 187
	},
}

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

Note that the connections originate from the ipfs daemon process.
		`,
197 198 199 200 201 202
	},
	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) {
203
		n, err := getNode(req)
204 205 206 207 208
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

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

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

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

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

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

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

242 243 244
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.
245
		`,
246 247 248 249
	},
	Arguments: []cmds.Argument{
		cmds.StringArg("Peer", true, false, "Remote peer to connect to"),
		cmds.StringArg("Protocol", true, false, "Protocol identifier."),
250
		cmds.StringArg("BindAddress", false, false, "Address to listen for connection/s (default: /ip4/127.0.0.1/tcp/0)."),
251 252
	},
	Run: func(req cmds.Request, res cmds.Response) {
253
		n, err := getNode(req)
254 255 256 257 258 259 260 261 262 263 264
		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
265
		proto := "/p2p/" + req.Arguments()[1]
266 267 268 269 270 271 272 273 274 275

		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
276
		listenerInfo, err := n.P2P.Dial(n.Context(), addr, peer, proto, bindAddr)
277 278 279 280 281
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

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

		res.SetOutput(&output)
	},
}

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

308 309 310 311 312 313 314 315 316
		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
317
			proto = "/p2p/" + req.Arguments()[0]
318 319
		}

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

Łukasz Magiera's avatar
Łukasz Magiera committed
332
var p2pStreamCloseCmd = &cmds.Command{
333 334 335 336 337 338 339 340 341 342 343 344 345
	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)
346 347 348 349
			return
		}

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

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

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

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

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

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

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

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

	return n, nil
397
}