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

import (
4
	"context"
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"
10 11
	"sync"
	"time"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
12

Overbool's avatar
Overbool committed
13 14 15 16
	commands "github.com/ipfs/go-ipfs/commands"
	cmdenv "github.com/ipfs/go-ipfs/core/commands/cmdenv"
	repo "github.com/ipfs/go-ipfs/repo"
	fsrepo "github.com/ipfs/go-ipfs/repo/fsrepo"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
17

Jakub Sztandera's avatar
Jakub Sztandera committed
18 19
	cmds "github.com/ipfs/go-ipfs-cmds"
	config "github.com/ipfs/go-ipfs-config"
Raúl Kripalani's avatar
Raúl Kripalani committed
20 21
	inet "github.com/libp2p/go-libp2p-core/network"
	peer "github.com/libp2p/go-libp2p-core/peer"
Jakub Sztandera's avatar
Jakub Sztandera committed
22
	ma "github.com/multiformats/go-multiaddr"
23
	madns "github.com/multiformats/go-multiaddr-dns"
24
	mamask "github.com/whyrusleeping/multiaddr-filter"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
25 26
)

27 28 29 30
const (
	dnsResolveTimeout = 10 * time.Second
)

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
31 32 33 34
type stringList struct {
	Strings []string
}

35 36 37 38
type addrMap struct {
	Addrs map[string][]string
}

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

Kejie Zhang's avatar
Kejie Zhang committed
57 58 59 60 61 62 63
const (
	swarmVerboseOptionName   = "verbose"
	swarmStreamsOptionName   = "streams"
	swarmLatencyOptionName   = "latency"
	swarmDirectionOptionName = "direction"
)

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
64
var swarmPeersCmd = &cmds.Command{
Steven Allen's avatar
Steven Allen committed
65
	Helptext: cmds.HelpText{
rht's avatar
rht committed
66
		Tagline: "List peers with open connections.",
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
67
		ShortDescription: `
68
'ipfs swarm peers' lists the set of peers this node is connected to.
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
69 70
`,
	},
Steven Allen's avatar
Steven Allen committed
71 72 73 74 75
	Options: []cmds.Option{
		cmds.BoolOption(swarmVerboseOptionName, "v", "display all extra information"),
		cmds.BoolOption(swarmStreamsOptionName, "Also list information about open streams for each peer"),
		cmds.BoolOption(swarmLatencyOptionName, "Also list information about latency to each peer"),
		cmds.BoolOption(swarmDirectionOptionName, "Also list information about the direction of connection"),
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
76
	},
77
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
78
		api, err := cmdenv.GetApi(env, req)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
79
		if err != nil {
80
			return err
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
81 82
		}

Kejie Zhang's avatar
Kejie Zhang committed
83 84 85 86
		verbose, _ := req.Options[swarmVerboseOptionName].(bool)
		latency, _ := req.Options[swarmLatencyOptionName].(bool)
		streams, _ := req.Options[swarmStreamsOptionName].(bool)
		direction, _ := req.Options[swarmDirectionOptionName].(bool)
87

88 89 90 91 92
		conns, err := api.Swarm().Peers(req.Context)
		if err != nil {
			return err
		}

93 94 95
		var out connInfos
		for _, c := range conns {
			ci := connInfo{
96 97
				Addr: c.Address().String(),
				Peer: c.ID().Pretty(),
98 99
			}

100
			if verbose || direction {
101
				// set direction
102
				ci.Direction = c.Direction()
103
			}
104

105
			if verbose || latency {
106
				lat, err := c.Latency()
107 108 109 110
				if err != nil {
					return err
				}

111 112 113 114 115
				if lat == 0 {
					ci.Latency = "n/a"
				} else {
					ci.Latency = lat.String()
				}
116 117
			}
			if verbose || streams {
118
				strs, err := c.Streams()
119 120 121
				if err != nil {
					return err
				}
122 123

				for _, s := range strs {
124
					ci.Streams = append(ci.Streams, streamInfo{Protocol: string(s)})
125
				}
126
			}
127 128
			sort.Sort(&ci)
			out.Peers = append(out.Peers, ci)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
129 130
		}

131
		sort.Sort(&out)
132
		return cmds.EmitOnce(res, &out)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
133
	},
134
	Encoders: cmds.EncoderMap{
Overbool's avatar
Overbool committed
135
		cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, ci *connInfos) error {
136
			pipfs := ma.ProtocolWithCode(ma.P_IPFS).Name
137
			for _, info := range ci.Peers {
138
				fmt.Fprintf(w, "%s/%s/%s", info.Addr, pipfs, info.Peer)
139
				if info.Latency != "" {
140
					fmt.Fprintf(w, " %s", info.Latency)
141
				}
142

143
				if info.Direction != inet.DirUnknown {
144
					fmt.Fprintf(w, " %s", directionString(info.Direction))
145
				}
146
				fmt.Fprintln(w)
147 148 149 150 151 152

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

153
					fmt.Fprintf(w, "  %s\n", s.Protocol)
154 155 156
				}
			}

157 158
			return nil
		}),
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
159
	},
160 161 162 163 164 165 166 167
	Type: connInfos{},
}

type streamInfo struct {
	Protocol string
}

type connInfo struct {
168 169 170 171 172 173
	Addr      string
	Peer      string
	Latency   string
	Muxer     string
	Direction inet.Direction
	Streams   []streamInfo
174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201
}

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
202 203
}

204 205 206 207 208 209 210 211
// directionString transfers to string
func directionString(d inet.Direction) string {
	switch d {
	case inet.DirInbound:
		return "inbound"
	case inet.DirOutbound:
		return "outbound"
	default:
212
		return ""
213 214 215
	}
}

216
var swarmAddrsCmd = &cmds.Command{
Steven Allen's avatar
Steven Allen committed
217
	Helptext: cmds.HelpText{
218
		Tagline: "List known addresses. Useful for debugging.",
219
		ShortDescription: `
220
'ipfs swarm addrs' lists all addresses this node is aware of.
221 222
`,
	},
223
	Subcommands: map[string]*cmds.Command{
224 225
		"local":  swarmAddrsLocalCmd,
		"listen": swarmAddrsListenCmd,
226
	},
227
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
228
		api, err := cmdenv.GetApi(env, req)
229
		if err != nil {
230
			return err
231 232
		}

233 234
		addrs, err := api.Swarm().KnownAddrs(req.Context)
		if err != nil {
235
			return err
236 237
		}

238 239
		out := make(map[string][]string)
		for p, paddrs := range addrs {
240
			s := p.Pretty()
241 242
			for _, a := range paddrs {
				out[s] = append(out[s], a.String())
243 244 245
			}
		}

246
		return cmds.EmitOnce(res, &addrMap{Addrs: out})
247
	},
248
	Encoders: cmds.EncoderMap{
Overbool's avatar
Overbool committed
249
		cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, am *addrMap) error {
250
			// sort the ids first
Overbool's avatar
Overbool committed
251 252
			ids := make([]string, 0, len(am.Addrs))
			for p := range am.Addrs {
253 254
				ids = append(ids, p)
			}
255
			sort.Strings(ids)
256 257

			for _, p := range ids {
Overbool's avatar
Overbool committed
258
				paddrs := am.Addrs[p]
259
				fmt.Fprintf(w, "%s (%d)\n", p, len(paddrs))
260
				for _, addr := range paddrs {
261
					fmt.Fprintf(w, "\t"+addr+"\n")
262 263
				}
			}
Overbool's avatar
Overbool committed
264

265 266
			return nil
		}),
267 268 269 270
	},
	Type: addrMap{},
}

271
var swarmAddrsLocalCmd = &cmds.Command{
Steven Allen's avatar
Steven Allen committed
272
	Helptext: cmds.HelpText{
273 274
		Tagline: "List local addresses.",
		ShortDescription: `
275
'ipfs swarm addrs local' lists all local listening addresses announced to the network.
276 277
`,
	},
Steven Allen's avatar
Steven Allen committed
278 279
	Options: []cmds.Option{
		cmds.BoolOption("id", "Show peer ID in addresses."),
280
	},
281
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
282
		api, err := cmdenv.GetApi(env, req)
283
		if err != nil {
284
			return err
285 286
		}

287 288 289
		showid, _ := req.Options["id"].(bool)
		self, err := api.Key().Self(req.Context)
		if err != nil {
290
			return err
291 292
		}

293 294 295 296
		maddrs, err := api.Swarm().LocalAddrs(req.Context)
		if err != nil {
			return err
		}
297 298

		var addrs []string
299
		p2pProtocolName := ma.ProtocolWithCode(ma.P_P2P).Name
300
		for _, addr := range maddrs {
301 302
			saddr := addr.String()
			if showid {
303
				saddr = path.Join(saddr, p2pProtocolName, self.ID().Pretty())
304 305 306
			}
			addrs = append(addrs, saddr)
		}
307
		sort.Strings(addrs)
308
		return cmds.EmitOnce(res, &stringList{addrs})
309 310
	},
	Type: stringList{},
311
	Encoders: cmds.EncoderMap{
Overbool's avatar
Overbool committed
312
		cmds.Text: cmds.MakeTypedEncoder(stringListEncoder),
313 314 315 316
	},
}

var swarmAddrsListenCmd = &cmds.Command{
Steven Allen's avatar
Steven Allen committed
317
	Helptext: cmds.HelpText{
318 319 320 321 322
		Tagline: "List interface listening addresses.",
		ShortDescription: `
'ipfs swarm addrs listen' lists all interface addresses the node is listening on.
`,
	},
323
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
324
		api, err := cmdenv.GetApi(env, req)
325
		if err != nil {
326
			return err
327 328 329
		}

		var addrs []string
330
		maddrs, err := api.Swarm().ListenAddrs(req.Context)
331
		if err != nil {
332
			return err
333 334 335 336 337
		}

		for _, addr := range maddrs {
			addrs = append(addrs, addr.String())
		}
338
		sort.Strings(addrs)
339

340
		return cmds.EmitOnce(res, &stringList{addrs})
341 342
	},
	Type: stringList{},
343
	Encoders: cmds.EncoderMap{
Overbool's avatar
Overbool committed
344
		cmds.Text: cmds.MakeTypedEncoder(stringListEncoder),
345 346 347
	},
}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
348
var swarmConnectCmd = &cmds.Command{
Steven Allen's avatar
Steven Allen committed
349
	Helptext: cmds.HelpText{
rht's avatar
rht committed
350
		Tagline: "Open connection to a given address.",
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
351
		ShortDescription: `
352 353
'ipfs swarm connect' opens a new direct connection to a peer address.

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

356
ipfs swarm connect /ip4/104.131.131.82/tcp/4001/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
357 358
`,
	},
Steven Allen's avatar
Steven Allen committed
359 360
	Arguments: []cmds.Argument{
		cmds.StringArg("address", true, true, "Address of peer to connect to.").EnableStdin(),
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
361
	},
362
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
363
		api, err := cmdenv.GetApi(env, req)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
364
		if err != nil {
365
			return err
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
366 367
		}

368
		addrs := req.Arguments
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
369

Steven Allen's avatar
Steven Allen committed
370
		pis, err := parseAddresses(req.Context, addrs)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
371
		if err != nil {
372
			return err
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
373 374
		}

375 376 377
		output := make([]string, len(pis))
		for i, pi := range pis {
			output[i] = "connect " + pi.ID.Pretty()
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
378

379
			err := api.Swarm().Connect(req.Context, pi)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
380
			if err != nil {
381
				return fmt.Errorf("%s failure: %s", output[i], err)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
382
			}
383
			output[i] += " success"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
384 385
		}

386
		return cmds.EmitOnce(res, &stringList{output})
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
387
	},
388
	Encoders: cmds.EncoderMap{
Overbool's avatar
Overbool committed
389
		cmds.Text: cmds.MakeTypedEncoder(stringListEncoder),
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
390
	},
391
	Type: stringList{},
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
392 393
}

394
var swarmDisconnectCmd = &cmds.Command{
Steven Allen's avatar
Steven Allen committed
395
	Helptext: cmds.HelpText{
rht's avatar
rht committed
396
		Tagline: "Close connection to a given address.",
397
		ShortDescription: `
398
'ipfs swarm disconnect' closes a connection to a peer address. The address
399
format is an IPFS multiaddr:
400

401
ipfs swarm disconnect /ip4/104.131.131.82/tcp/4001/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ
402

403 404
The disconnect is not permanent; if ipfs needs to talk to that address later,
it will reconnect.
405 406
`,
	},
Steven Allen's avatar
Steven Allen committed
407 408
	Arguments: []cmds.Argument{
		cmds.StringArg("address", true, true, "Address of peer to disconnect from.").EnableStdin(),
409
	},
410
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
411
		api, err := cmdenv.GetApi(env, req)
412
		if err != nil {
413
			return err
414 415
		}

Steven Allen's avatar
Steven Allen committed
416
		addrs, err := parseAddresses(req.Context, req.Arguments)
417
		if err != nil {
418
			return err
419 420
		}

Steven Allen's avatar
Steven Allen committed
421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443
		output := make([]string, 0, len(addrs))
		for _, ainfo := range addrs {
			maddrs, err := peer.AddrInfoToP2pAddrs(&ainfo)
			if err != nil {
				return err
			}
			// FIXME: This will print:
			//
			//   disconnect QmFoo success
			//   disconnect QmFoo success
			//   ...
			//
			// Once per address specified. However, I'm not sure of
			// a good backwards compat solution. Right now, I'm just
			// preserving the current behavior.
			for _, addr := range maddrs {
				msg := "disconnect " + ainfo.ID.Pretty()
				if err := api.Swarm().Disconnect(req.Context, addr); err != nil {
					msg += " failure: " + err.Error()
				} else {
					msg += " success"
				}
				output = append(output, msg)
444 445
			}
		}
446
		return cmds.EmitOnce(res, &stringList{output})
447
	},
448
	Encoders: cmds.EncoderMap{
Overbool's avatar
Overbool committed
449
		cmds.Text: cmds.MakeTypedEncoder(stringListEncoder),
450 451 452 453
	},
	Type: stringList{},
}

454
// parseAddresses is a function that takes in a slice of string peer addresses
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
455
// (multiaddr + peerid) and returns a slice of properly constructed peers
Steven Allen's avatar
Steven Allen committed
456
func parseAddresses(ctx context.Context, addrs []string) ([]peer.AddrInfo, error) {
457
	// resolve addresses
458
	maddrs, err := resolveAddresses(ctx, addrs)
459 460 461 462
	if err != nil {
		return nil, err
	}

Steven Allen's avatar
Steven Allen committed
463
	return peer.AddrInfosFromP2pAddrs(maddrs...)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
464
}
465

466
// resolveAddresses resolves addresses parallelly
467 468 469 470
func resolveAddresses(ctx context.Context, addrs []string) ([]ma.Multiaddr, error) {
	ctx, cancel := context.WithTimeout(ctx, dnsResolveTimeout)
	defer cancel()

471 472 473 474
	var maddrs []ma.Multiaddr
	var wg sync.WaitGroup
	resolveErrC := make(chan error, len(addrs))

475 476
	maddrC := make(chan ma.Multiaddr)

477 478 479 480 481
	for _, addr := range addrs {
		maddr, err := ma.NewMultiaddr(addr)
		if err != nil {
			return nil, err
		}
482

483
		// check whether address ends in `ipfs/Qm...`
484
		if _, last := ma.SplitLast(maddr); last.Protocol().Code == ma.P_IPFS {
485 486 487 488 489 490 491 492 493 494 495 496
			maddrs = append(maddrs, maddr)
			continue
		}
		wg.Add(1)
		go func(maddr ma.Multiaddr) {
			defer wg.Done()
			raddrs, err := madns.Resolve(ctx, maddr)
			if err != nil {
				resolveErrC <- err
				return
			}
			// filter out addresses that still doesn't end in `ipfs/Qm...`
497
			found := 0
498
			for _, raddr := range raddrs {
Steven Allen's avatar
Steven Allen committed
499
				if _, last := ma.SplitLast(raddr); last != nil && last.Protocol().Code == ma.P_IPFS {
500
					maddrC <- raddr
501
					found++
502 503
				}
			}
504
			if found == 0 {
505
				resolveErrC <- fmt.Errorf("found no ipfs peers at %s", maddr)
506
			}
507 508
		}(maddr)
	}
509 510 511 512 513 514 515 516
	go func() {
		wg.Wait()
		close(maddrC)
	}()

	for maddr := range maddrC {
		maddrs = append(maddrs, maddr)
	}
517 518 519 520 521 522 523 524 525 526

	select {
	case err := <-resolveErrC:
		return nil, err
	default:
	}

	return maddrs, nil
}

527
var swarmFiltersCmd = &cmds.Command{
Steven Allen's avatar
Steven Allen committed
528
	Helptext: cmds.HelpText{
rht's avatar
rht committed
529
		Tagline: "Manipulate address filters.",
530
		ShortDescription: `
531 532 533
'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:
534

535
Example:
536 537 538 539 540 541 542

    /ip4/192.168.0.0/ipcidr/16

Where the above is equivalent to the standard CIDR:

    192.168.0.0/16

543
Filters default to those specified under the "Swarm.AddrFilters" config key.
544 545 546 547 548 549
`,
	},
	Subcommands: map[string]*cmds.Command{
		"add": swarmFiltersAddCmd,
		"rm":  swarmFiltersRmCmd,
	},
550 551
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
		n, err := cmdenv.GetNode(env)
552
		if err != nil {
553
			return err
554 555
		}

556
		if n.PeerHost == nil {
557
			return ErrNotOnline
558 559
		}

560
		var output []string
Steven Allen's avatar
Steven Allen committed
561
		for _, f := range n.Filters.FiltersForAction(ma.ActionDeny) {
562
			s, err := mamask.ConvertIPNet(&f)
563
			if err != nil {
564
				return err
565 566 567
			}
			output = append(output, s)
		}
568
		return cmds.EmitOnce(res, &stringList{output})
569
	},
570
	Encoders: cmds.EncoderMap{
Overbool's avatar
Overbool committed
571
		cmds.Text: cmds.MakeTypedEncoder(stringListEncoder),
572 573 574 575 576
	},
	Type: stringList{},
}

var swarmFiltersAddCmd = &cmds.Command{
Steven Allen's avatar
Steven Allen committed
577
	Helptext: cmds.HelpText{
rht's avatar
rht committed
578
		Tagline: "Add an address filter.",
579
		ShortDescription: `
580
'ipfs swarm filters add' will add an address filter to the daemons swarm.
581 582
`,
	},
Steven Allen's avatar
Steven Allen committed
583 584
	Arguments: []cmds.Argument{
		cmds.StringArg("address", true, true, "Multiaddr to filter.").EnableStdin(),
585
	},
586 587
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
		n, err := cmdenv.GetNode(env)
588
		if err != nil {
589
			return err
590 591
		}

592
		if n.PeerHost == nil {
593
			return ErrNotOnline
594 595
		}

596 597
		if len(req.Arguments) == 0 {
			return errors.New("no filters to add")
598 599
		}

600
		r, err := fsrepo.Open(env.(*commands.Context).ConfigRoot)
601
		if err != nil {
602
			return err
603 604 605 606
		}
		defer r.Close()
		cfg, err := r.Config()
		if err != nil {
607
			return err
608 609
		}

610
		for _, arg := range req.Arguments {
611
			mask, err := mamask.NewMask(arg)
612
			if err != nil {
613
				return err
614 615
			}

Steven Allen's avatar
Steven Allen committed
616
			n.Filters.AddFilter(*mask, ma.ActionDeny)
617
		}
618

619
		added, err := filtersAdd(r, cfg, req.Arguments)
620
		if err != nil {
621
			return err
622 623
		}

624
		return cmds.EmitOnce(res, &stringList{added})
625
	},
626
	Encoders: cmds.EncoderMap{
Overbool's avatar
Overbool committed
627
		cmds.Text: cmds.MakeTypedEncoder(stringListEncoder),
628 629
	},
	Type: stringList{},
630 631 632
}

var swarmFiltersRmCmd = &cmds.Command{
Steven Allen's avatar
Steven Allen committed
633
	Helptext: cmds.HelpText{
rht's avatar
rht committed
634
		Tagline: "Remove an address filter.",
635
		ShortDescription: `
636
'ipfs swarm filters rm' will remove an address filter from the daemons swarm.
637 638
`,
	},
Steven Allen's avatar
Steven Allen committed
639 640
	Arguments: []cmds.Argument{
		cmds.StringArg("address", true, true, "Multiaddr filter to remove.").EnableStdin(),
641
	},
642 643
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
		n, err := cmdenv.GetNode(env)
644
		if err != nil {
645
			return err
646 647 648
		}

		if n.PeerHost == nil {
649
			return ErrNotOnline
650 651
		}

652
		r, err := fsrepo.Open(env.(*commands.Context).ConfigRoot)
653
		if err != nil {
654
			return err
655 656 657 658
		}
		defer r.Close()
		cfg, err := r.Config()
		if err != nil {
659
			return err
660 661
		}

662
		if req.Arguments[0] == "all" || req.Arguments[0] == "*" {
Steven Allen's avatar
Steven Allen committed
663
			fs := n.Filters.FiltersForAction(ma.ActionDeny)
664
			for _, f := range fs {
Steven Allen's avatar
Steven Allen committed
665
				n.Filters.RemoveLiteral(f)
666
			}
667 668 669

			removed, err := filtersRemoveAll(r, cfg)
			if err != nil {
670
				return err
671 672
			}

673
			return cmds.EmitOnce(res, &stringList{removed})
674 675
		}

676
		for _, arg := range req.Arguments {
677
			mask, err := mamask.NewMask(arg)
678
			if err != nil {
679
				return err
680 681
			}

Steven Allen's avatar
Steven Allen committed
682
			n.Filters.RemoveLiteral(*mask)
683
		}
684

685
		removed, err := filtersRemove(r, cfg, req.Arguments)
686
		if err != nil {
687
			return err
688
		}
689

690
		return cmds.EmitOnce(res, &stringList{removed})
691
	},
692
	Encoders: cmds.EncoderMap{
Overbool's avatar
Overbool committed
693
		cmds.Text: cmds.MakeTypedEncoder(stringListEncoder),
694
	},
695
	Type: stringList{},
696
}
697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732

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
}
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 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772

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
}