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

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

11
	cmds "github.com/ipfs/go-ipfs/commands"
12 13 14
	repo "github.com/ipfs/go-ipfs/repo"
	config "github.com/ipfs/go-ipfs/repo/config"
	"github.com/ipfs/go-ipfs/repo/fsrepo"
15
	iaddr "github.com/ipfs/go-ipfs/thirdparty/ipfsaddr"
16
	pstore "gx/ipfs/QmXXCcQ7CLg5a81Ui9TTR35QcR4y7ZyihxwfjqaHfUVcVo/go-libp2p-peerstore"
Jeromy's avatar
Jeromy committed
17
	swarm "gx/ipfs/QmcjMKTqrWgMMCExEnwczefhno5fvx7FHDV63peZwDzHNF/go-libp2p-swarm"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
18

19
	mafilter "gx/ipfs/QmSMZwvs3n4GBikZ7hKzT17c3bk65FmyZo2JqtJ16swqCv/multiaddr-filter"
20
	ma "gx/ipfs/QmUAQaWbKxGCUTuoQVvvicbQNZ9APF5pDGWyAZSe93AtKH/go-multiaddr"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
21 22 23 24 25 26
)

type stringList struct {
	Strings []string
}

27 28 29 30
type addrMap struct {
	Addrs map[string][]string
}

31
var SwarmCmd = &cmds.Command{
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
32
	Helptext: cmds.HelpText{
33
		Tagline: "Interact with the swarm.",
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
34
		ShortDescription: `
35
'ipfs swarm' is a tool to manipulate the network swarm. The swarm is the
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
36 37 38 39 40
component that opens, listens for, and maintains connections to other
ipfs peers in the internet.
`,
	},
	Subcommands: map[string]*cmds.Command{
41
		"addrs":      swarmAddrsCmd,
42 43
		"connect":    swarmConnectCmd,
		"disconnect": swarmDisconnectCmd,
44
		"filters":    swarmFiltersCmd,
Richard Littauer's avatar
Richard Littauer committed
45
		"peers":      swarmPeersCmd,
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
46 47 48 49 50
	},
}

var swarmPeersCmd = &cmds.Command{
	Helptext: cmds.HelpText{
rht's avatar
rht committed
51
		Tagline: "List peers with open connections.",
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
52
		ShortDescription: `
53
'ipfs swarm peers' lists the set of peers this node is connected to.
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
54 55
`,
	},
56
	Options: []cmds.Option{
57 58 59
		cmds.BoolOption("verbose", "v", "display all extra information"),
		cmds.BoolOption("streams", "Also list information about open streams for each peer"),
		cmds.BoolOption("latency", "Also list information about latency to each peer"),
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
60
	},
61
	Run: func(req cmds.Request, res cmds.Response) {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
62 63

		log.Debug("ipfs swarm peers")
Jeromy's avatar
Jeromy committed
64
		n, err := req.InvocContext().GetNode()
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
65
		if err != nil {
66 67
			res.SetError(err, cmds.ErrNormal)
			return
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
68 69
		}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
70
		if n.PeerHost == nil {
71 72
			res.SetError(errNotOnline, cmds.ErrClient)
			return
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
73 74
		}

75
		verbose, _, _ := req.Option("verbose").Bool()
76 77 78
		latency, _, _ := req.Option("latency").Bool()
		streams, _, _ := req.Option("streams").Bool()

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
79
		conns := n.PeerHost.Network().Conns()
80

81 82
		var out connInfos
		for _, c := range conns {
83
			pid := c.RemotePeer()
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
84
			addr := c.RemoteMultiaddr()
85

86 87 88 89 90
			ci := connInfo{
				Addr: addr.String(),
				Peer: pid.Pretty(),
			}

Jeromy's avatar
Jeromy committed
91 92 93 94 95
			swcon, ok := c.(*swarm.Conn)
			if ok {
				ci.Muxer = fmt.Sprintf("%T", swcon.StreamConn().Conn())
			}

96 97 98 99 100 101 102 103 104 105 106 107 108
			if verbose || latency {
				ci.Latency = n.Peerstore.LatencyEWMA(pid).String()
			}
			if verbose || streams {
				strs, err := c.GetStreams()
				if err != nil {
					res.SetError(err, cmds.ErrNormal)
					return
				}

				for _, s := range strs {
					ci.Streams = append(ci.Streams, streamInfo{Protocol: string(s.Protocol())})
				}
109
			}
110 111
			sort.Sort(&ci)
			out.Peers = append(out.Peers, ci)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
112 113
		}

114 115
		sort.Sort(&out)
		res.SetOutput(&out)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
116 117
	},
	Marshalers: cmds.MarshalerMap{
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
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
			ci, ok := res.Output().(*connInfos)
			if !ok {
				return nil, fmt.Errorf("expected output type to be connInfos")
			}

			buf := new(bytes.Buffer)
			for _, info := range ci.Peers {
				fmt.Fprintf(buf, "%s/ipfs/%s", info.Addr, info.Peer)
				if info.Latency != "" {
					fmt.Fprintf(buf, " %s", info.Latency)
				}
				fmt.Fprintln(buf)

				for _, s := range info.Streams {
					if s.Protocol == "" {
						s.Protocol = "<no protocol name>"
					}

					fmt.Fprintf(buf, "  %s\n", s.Protocol)
				}
			}

			return buf, nil
		},
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
143
	},
144 145 146 147 148 149 150 151 152 153 154
	Type: connInfos{},
}

type streamInfo struct {
	Protocol string
}

type connInfo struct {
	Addr    string
	Peer    string
	Latency string
Jeromy's avatar
Jeromy committed
155
	Muxer   string
156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184
	Streams []streamInfo
}

func (ci *connInfo) Less(i, j int) bool {
	return ci.Streams[i].Protocol < ci.Streams[j].Protocol
}

func (ci *connInfo) Len() int {
	return len(ci.Streams)
}

func (ci *connInfo) Swap(i, j int) {
	ci.Streams[i], ci.Streams[j] = ci.Streams[j], ci.Streams[i]
}

type connInfos struct {
	Peers []connInfo
}

func (ci connInfos) Less(i, j int) bool {
	return ci.Peers[i].Addr < ci.Peers[j].Addr
}

func (ci connInfos) Len() int {
	return len(ci.Peers)
}

func (ci connInfos) Swap(i, j int) {
	ci.Peers[i], ci.Peers[j] = ci.Peers[j], ci.Peers[i]
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
185 186
}

187 188
var swarmAddrsCmd = &cmds.Command{
	Helptext: cmds.HelpText{
189
		Tagline: "List known addresses. Useful for debugging.",
190
		ShortDescription: `
191
'ipfs swarm addrs' lists all addresses this node is aware of.
192 193
`,
	},
194 195 196
	Subcommands: map[string]*cmds.Command{
		"local": swarmAddrsLocalCmd,
	},
197 198
	Run: func(req cmds.Request, res cmds.Response) {

Jeromy's avatar
Jeromy committed
199
		n, err := req.InvocContext().GetNode()
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
		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))

236
			buf := new(bytes.Buffer)
237 238 239 240 241 242 243
			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")
				}
			}
244
			return buf, nil
245 246 247 248 249
		},
	},
	Type: addrMap{},
}

250 251 252 253
var swarmAddrsLocalCmd = &cmds.Command{
	Helptext: cmds.HelpText{
		Tagline: "List local addresses.",
		ShortDescription: `
254
'ipfs swarm addrs local' lists all local addresses the node is listening on.
255 256 257
`,
	},
	Options: []cmds.Option{
258
		cmds.BoolOption("id", "Show peer ID in addresses.").Default(false),
259 260 261
	},
	Run: func(req cmds.Request, res cmds.Response) {

Jeromy's avatar
Jeromy committed
262
		n, err := req.InvocContext().GetNode()
263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		if n.PeerHost == nil {
			res.SetError(errNotOnline, cmds.ErrClient)
			return
		}

		showid, _, _ := req.Option("id").Bool()
		id := n.Identity.Pretty()

		var addrs []string
		for _, addr := range n.PeerHost.Addrs() {
			saddr := addr.String()
			if showid {
				saddr = path.Join(saddr, "ipfs", id)
			}
			addrs = append(addrs, saddr)
		}
		sort.Sort(sort.StringSlice(addrs))

		res.SetOutput(&stringList{addrs})
	},
	Type: stringList{},
	Marshalers: cmds.MarshalerMap{
		cmds.Text: stringListMarshaler,
	},
}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
294 295
var swarmConnectCmd = &cmds.Command{
	Helptext: cmds.HelpText{
rht's avatar
rht committed
296
		Tagline: "Open connection to a given address.",
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
297
		ShortDescription: `
298 299
'ipfs swarm connect' opens a new direct connection to a peer address.

300
The address format is an IPFS multiaddr:
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
301

302
ipfs swarm connect /ip4/104.131.131.82/tcp/4001/ipfs/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
303 304 305
`,
	},
	Arguments: []cmds.Argument{
Jeromy's avatar
Jeromy committed
306
		cmds.StringArg("address", true, true, "Address of peer to connect to.").EnableStdin(),
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
307
	},
308
	Run: func(req cmds.Request, res cmds.Response) {
309
		ctx := req.Context()
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
310

Jeromy's avatar
Jeromy committed
311
		n, err := req.InvocContext().GetNode()
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
312
		if err != nil {
313 314
			res.SetError(err, cmds.ErrNormal)
			return
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
315 316
		}

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

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
319
		if n.PeerHost == nil {
320 321
			res.SetError(errNotOnline, cmds.ErrClient)
			return
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
322 323
		}

Jeromy's avatar
Jeromy committed
324 325 326 327 328 329 330 331
		snet, ok := n.PeerHost.Network().(*swarm.Network)
		if !ok {
			res.SetError(fmt.Errorf("peerhost network was not swarm"), cmds.ErrNormal)
			return
		}

		swrm := snet.Swarm()

332
		pis, err := peersWithAddresses(addrs)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
333
		if err != nil {
334 335
			res.SetError(err, cmds.ErrNormal)
			return
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
336 337
		}

338 339
		output := make([]string, len(pis))
		for i, pi := range pis {
Jeromy's avatar
Jeromy committed
340 341
			swrm.Backoff().Clear(pi.ID)

342
			output[i] = "connect " + pi.ID.Pretty()
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
343

344
			err := n.PeerHost.Connect(ctx, pi)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
345
			if err != nil {
346 347
				res.SetError(fmt.Errorf("%s failure: %s", output[i], err), cmds.ErrNormal)
				return
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
348
			}
349
			output[i] += " success"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
350 351
		}

352
		res.SetOutput(&stringList{output})
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
353 354 355 356
	},
	Marshalers: cmds.MarshalerMap{
		cmds.Text: stringListMarshaler,
	},
357
	Type: stringList{},
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
358 359
}

360 361
var swarmDisconnectCmd = &cmds.Command{
	Helptext: cmds.HelpText{
rht's avatar
rht committed
362
		Tagline: "Close connection to a given address.",
363
		ShortDescription: `
364
'ipfs swarm disconnect' closes a connection to a peer address. The address
365
format is an IPFS multiaddr:
366 367

ipfs swarm disconnect /ip4/104.131.131.82/tcp/4001/ipfs/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ
368

369 370
The disconnect is not permanent; if ipfs needs to talk to that address later,
it will reconnect.
371 372 373
`,
	},
	Arguments: []cmds.Argument{
Jeromy's avatar
Jeromy committed
374
		cmds.StringArg("address", true, true, "Address of peer to disconnect from.").EnableStdin(),
375 376
	},
	Run: func(req cmds.Request, res cmds.Response) {
Jeromy's avatar
Jeromy committed
377
		n, err := req.InvocContext().GetNode()
378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404
		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) {
405
					log.Debug("it's not", conn.RemoteMultiaddr(), taddr)
406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429
					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{},
}

430
func stringListMarshaler(res cmds.Response) (io.Reader, error) {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
431 432 433 434 435
	list, ok := res.Output().(*stringList)
	if !ok {
		return nil, errors.New("failed to cast []string")
	}

436
	buf := new(bytes.Buffer)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
437
	for _, s := range list.Strings {
438 439
		buf.WriteString(s)
		buf.WriteString("\n")
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
440
	}
441
	return buf, nil
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
442
}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
443

444
// parseAddresses is a function that takes in a slice of string peer addresses
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
445
// (multiaddr + peerid) and returns slices of multiaddrs and peerids.
446 447 448 449
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)
450
		if err != nil {
451
			return nil, cmds.ClientError("invalid peer address: " + err.Error())
452
		}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
453 454 455 456 457 458
	}
	return
}

// peersWithAddresses is a function that takes in a slice of string peer addresses
// (multiaddr + peerid) and returns a slice of properly constructed peers
Jeromy's avatar
Jeromy committed
459
func peersWithAddresses(addrs []string) (pis []pstore.PeerInfo, err error) {
460
	iaddrs, err := parseAddresses(addrs)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
461 462 463 464
	if err != nil {
		return nil, err
	}

465
	for _, iaddr := range iaddrs {
Jeromy's avatar
Jeromy committed
466
		pis = append(pis, pstore.PeerInfo{
467 468 469
			ID:    iaddr.ID(),
			Addrs: []ma.Multiaddr{iaddr.Transport()},
		})
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
470
	}
471
	return pis, nil
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
472
}
473 474 475

var swarmFiltersCmd = &cmds.Command{
	Helptext: cmds.HelpText{
rht's avatar
rht committed
476
		Tagline: "Manipulate address filters.",
477
		ShortDescription: `
478 479 480
'ipfs swarm filters' will list out currently applied filters. Its subcommands
can be used to add or remove said filters. Filters are specified using the
multiaddr-filter format:
481

482
Example:
483 484 485 486 487 488 489

    /ip4/192.168.0.0/ipcidr/16

Where the above is equivalent to the standard CIDR:

    192.168.0.0/16

490
Filters default to those specified under the "Swarm.AddrFilters" config key.
491 492 493 494 495 496 497
`,
	},
	Subcommands: map[string]*cmds.Command{
		"add": swarmFiltersAddCmd,
		"rm":  swarmFiltersRmCmd,
	},
	Run: func(req cmds.Request, res cmds.Response) {
Jeromy's avatar
Jeromy committed
498
		n, err := req.InvocContext().GetNode()
499 500 501 502 503
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

504 505 506 507 508
		if n.PeerHost == nil {
			res.SetError(errNotOnline, cmds.ErrNormal)
			return
		}

509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533
		snet, ok := n.PeerHost.Network().(*swarm.Network)
		if !ok {
			res.SetError(errors.New("failed to cast network to swarm network"), cmds.ErrNormal)
			return
		}

		var output []string
		for _, f := range snet.Filters.Filters() {
			s, err := mafilter.ConvertIPNet(f)
			if err != nil {
				res.SetError(err, cmds.ErrNormal)
				return
			}
			output = append(output, s)
		}
		res.SetOutput(&stringList{output})
	},
	Marshalers: cmds.MarshalerMap{
		cmds.Text: stringListMarshaler,
	},
	Type: stringList{},
}

var swarmFiltersAddCmd = &cmds.Command{
	Helptext: cmds.HelpText{
rht's avatar
rht committed
534
		Tagline: "Add an address filter.",
535
		ShortDescription: `
536
'ipfs swarm filters add' will add an address filter to the daemons swarm.
Richard Littauer's avatar
Richard Littauer committed
537
Filters applied this way will not persist daemon reboots, to achieve that,
538 539 540 541
add your filters to the ipfs config file.
`,
	},
	Arguments: []cmds.Argument{
Jeromy's avatar
Jeromy committed
542
		cmds.StringArg("address", true, true, "Multiaddr to filter.").EnableStdin(),
543 544
	},
	Run: func(req cmds.Request, res cmds.Response) {
Jeromy's avatar
Jeromy committed
545
		n, err := req.InvocContext().GetNode()
546 547 548 549 550
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

551 552 553 554 555
		if n.PeerHost == nil {
			res.SetError(errNotOnline, cmds.ErrNormal)
			return
		}

556 557 558 559 560 561
		snet, ok := n.PeerHost.Network().(*swarm.Network)
		if !ok {
			res.SetError(errors.New("failed to cast network to swarm network"), cmds.ErrNormal)
			return
		}

562 563 564 565 566
		if len(req.Arguments()) == 0 {
			res.SetError(errors.New("no filters to add"), cmds.ErrClient)
			return
		}

567 568 569 570 571 572 573 574 575 576 577 578
		r, err := fsrepo.Open(req.InvocContext().ConfigRoot)
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}
		defer r.Close()
		cfg, err := r.Config()
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

579 580 581 582 583 584 585 586 587
		for _, arg := range req.Arguments() {
			mask, err := mafilter.NewMask(arg)
			if err != nil {
				res.SetError(err, cmds.ErrNormal)
				return
			}

			snet.Filters.AddDialFilter(mask)
		}
588 589 590 591 592 593 594 595 596

		added, err := filtersAdd(r, cfg, req.Arguments())
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return

		}

		res.SetOutput(&stringList{added})
597
	},
598 599 600 601
	Marshalers: cmds.MarshalerMap{
		cmds.Text: stringListMarshaler,
	},
	Type: stringList{},
602 603 604 605
}

var swarmFiltersRmCmd = &cmds.Command{
	Helptext: cmds.HelpText{
rht's avatar
rht committed
606
		Tagline: "Remove an address filter.",
607
		ShortDescription: `
608
'ipfs swarm filters rm' will remove an address filter from the daemons swarm.
Richard Littauer's avatar
Richard Littauer committed
609
Filters removed this way will not persist daemon reboots, to achieve that,
610 611 612 613
remove your filters from the ipfs config file.
`,
	},
	Arguments: []cmds.Argument{
Jeromy's avatar
Jeromy committed
614
		cmds.StringArg("address", true, true, "Multiaddr filter to remove.").EnableStdin(),
615 616
	},
	Run: func(req cmds.Request, res cmds.Response) {
Jeromy's avatar
Jeromy committed
617
		n, err := req.InvocContext().GetNode()
618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		if n.PeerHost == nil {
			res.SetError(errNotOnline, cmds.ErrNormal)
			return
		}

		snet, ok := n.PeerHost.Network().(*swarm.Network)
		if !ok {
			res.SetError(errors.New("failed to cast network to swarm network"), cmds.ErrNormal)
			return
		}

634 635 636 637 638 639 640 641 642 643 644 645
		r, err := fsrepo.Open(req.InvocContext().ConfigRoot)
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}
		defer r.Close()
		cfg, err := r.Config()
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

646 647 648 649 650
		if req.Arguments()[0] == "all" || req.Arguments()[0] == "*" {
			fs := snet.Filters.Filters()
			for _, f := range fs {
				snet.Filters.Remove(f)
			}
651 652 653 654 655 656 657 658 659

			removed, err := filtersRemoveAll(r, cfg)
			if err != nil {
				res.SetError(err, cmds.ErrNormal)
				return
			}

			res.SetOutput(&stringList{removed})

660 661 662 663 664 665 666 667 668 669 670 671
			return
		}

		for _, arg := range req.Arguments() {
			mask, err := mafilter.NewMask(arg)
			if err != nil {
				res.SetError(err, cmds.ErrNormal)
				return
			}

			snet.Filters.Remove(mask)
		}
672 673 674 675 676 677 678

		removed, err := filtersRemove(r, cfg, req.Arguments())

		res.SetOutput(&stringList{removed})
	},
	Marshalers: cmds.MarshalerMap{
		cmds.Text: stringListMarshaler,
679
	},
680
	Type: stringList{},
681
}
682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717

func filtersAdd(r repo.Repo, cfg *config.Config, filters []string) ([]string, error) {
	addedMap := map[string]struct{}{}
	addedList := make([]string, 0, len(filters))

	// re-add cfg swarm filters to rm dupes
	oldFilters := cfg.Swarm.AddrFilters
	cfg.Swarm.AddrFilters = nil

	// add new filters
	for _, filter := range filters {
		if _, found := addedMap[filter]; found {
			continue
		}

		cfg.Swarm.AddrFilters = append(cfg.Swarm.AddrFilters, filter)
		addedList = append(addedList, filter)
		addedMap[filter] = struct{}{}
	}

	// add back original filters. in this order so that we output them.
	for _, filter := range oldFilters {
		if _, found := addedMap[filter]; found {
			continue
		}

		cfg.Swarm.AddrFilters = append(cfg.Swarm.AddrFilters, filter)
		addedMap[filter] = struct{}{}
	}

	if err := r.SetConfig(cfg); err != nil {
		return nil, err
	}

	return addedList, nil
}
718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757

func filtersRemoveAll(r repo.Repo, cfg *config.Config) ([]string, error) {
	removed := cfg.Swarm.AddrFilters
	cfg.Swarm.AddrFilters = nil

	if err := r.SetConfig(cfg); err != nil {
		return nil, err
	}

	return removed, nil
}

func filtersRemove(r repo.Repo, cfg *config.Config, toRemoveFilters []string) ([]string, error) {
	removed := make([]string, 0, len(toRemoveFilters))
	keep := make([]string, 0, len(cfg.Swarm.AddrFilters))

	oldFilters := cfg.Swarm.AddrFilters

	for _, oldFilter := range oldFilters {
		found := false
		for _, toRemoveFilter := range toRemoveFilters {
			if oldFilter == toRemoveFilter {
				found = true
				removed = append(removed, toRemoveFilter)
				break
			}
		}

		if !found {
			keep = append(keep, oldFilter)
		}
	}
	cfg.Swarm.AddrFilters = keep

	if err := r.SetConfig(cfg); err != nil {
		return nil, err
	}

	return removed, nil
}