add command to manipulate address filters and a sharness test for them
License: MIT Signed-off-by: Jeromy <jeromyj@gmail.com>
Jeromy committed
Jun 30, 2015 at 18:25 UTC
7cc73f7b867469d8875e129f9acf6c41baeaeb85
9 files changed
+403
-11
Godeps/Godeps.json
+1
-1
@@ -282,7 +282,7 @@
282
},
283
{
284
"ImportPath": "github.com/whyrusleeping/multiaddr-filter",
285
- "Rev": "15837fcc356fddef27c634b0f6379b3b7f259114"
285
+ "Rev": "9e26222151125ecd3fc1fd190179b6bdd55f5608"
286
},
287
{
288
"ImportPath": "golang.org/x/crypto/blowfish",
Godeps/_workspace/src/github.com/whyrusleeping/multiaddr-filter/LICENSE
new
+21
@@ -0,0 +1,21 @@
1
+The MIT License (MIT)
2
+
3
+Copyright (c) 2015 Jeromy Johnson
4
+
5
+Permission is hereby granted, free of charge, to any person obtaining a copy
6
+of this software and associated documentation files (the "Software"), to deal
7
+in the Software without restriction, including without limitation the rights
8
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+copies of the Software, and to permit persons to whom the Software is
10
+furnished to do so, subject to the following conditions:
11
+
12
+The above copyright notice and this permission notice shall be included in
13
+all copies or substantial portions of the Software.
14
+
15
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+THE SOFTWARE.
Godeps/_workspace/src/github.com/whyrusleeping/multiaddr-filter/README.md
new
+15
@@ -0,0 +1,15 @@
1
+# go-multiaddr-filter -- CIDR netmasks with multiaddr
2
+
3
+This module creates very simple [multiaddr](https://github.com/jbenet/go-multiaddr) formatted cidr netmasks.
4
+
5
+It doesn't do full multiaddr parsing to save on vendoring things and perf. The `net` package will take care of verifying the validity of the network part anyway.
6
+
7
+## Usage
8
+
9
+```go
10
+
11
+import filter "github.com/whyrusleeping/multiaddr-filter"
12
+
13
+filter.NewMask("/ip4/192.168.0.0/24") // ipv4
14
+filter.NewMask("/ip6/fe80::/64") // ipv6
15
+```
Godeps/_workspace/src/github.com/whyrusleeping/multiaddr-filter/mask.go
+35
-7
@@ -2,18 +2,46 @@ package mask
2
3
import (
4
"errors"
5
+ "fmt"
6
"net"
7
"strings"
8
+
9
+ manet "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr-net"
10
)
11
12
+var ErrInvalidFormat = errors.New("invalid multiaddr-filter format")
13
+
14
func NewMask(a string) (*net.IPNet, error) {
15
parts := strings.Split(a, "/")
11
- if len(parts) == 5 && parts[1] == "ip4" && parts[3] == "ipcidr" {
12
- _, ipn, err := net.ParseCIDR(parts[2] + "/" + parts[4])
13
- if err != nil {
14
- return nil, err
15
- }
16
- return ipn, nil
16
+
17
+ if parts[0] != "" {
18
+ return nil, ErrInvalidFormat
19
+ }
20
+
21
+ if len(parts) != 5 {
22
+ return nil, ErrInvalidFormat
23
+ }
24
+
25
+ // check it's a valid filter address. ip + cidr
26
+ isip := parts[1] == "ip4" || parts[1] == "ip6"
27
+ iscidr := parts[3] == "ipcidr"
28
+ if !isip || !iscidr {
29
+ return nil, ErrInvalidFormat
30
}
18
- return nil, errors.New("invalid format")
31
+
32
+ _, ipn, err := net.ParseCIDR(parts[2] + "/" + parts[4])
33
+ if err != nil {
34
+ return nil, err
35
+ }
36
+ return ipn, nil
37
+}
38
+
39
+func ConvertIPNet(n *net.IPNet) (string, error) {
40
+ addr, err := manet.FromIP(n.IP)
41
+ if err != nil {
42
+ return "", err
43
+ }
44
+
45
+ b, _ := n.Mask.Size()
46
+ return fmt.Sprintf("%s/ipcidr/%d", addr, b), nil
47
}
Godeps/_workspace/src/github.com/whyrusleeping/multiaddr-filter/mask_test.go
+96
@@ -5,6 +5,74 @@ import (
5
"testing"
6
)
7
8
+func TestValidMasks(t *testing.T) {
9
+
10
+ cidrOrFatal := func(s string) *net.IPNet {
11
+ _, ipn, err := net.ParseCIDR(s)
12
+ if err != nil {
13
+ t.Fatal(err)
14
+ }
15
+ return ipn
16
+ }
17
+
18
+ testCases := map[string]*net.IPNet{
19
+ "/ip4/1.2.3.4/ipcidr/0": cidrOrFatal("1.2.3.4/0"),
20
+ "/ip4/1.2.3.4/ipcidr/32": cidrOrFatal("1.2.3.4/32"),
21
+ "/ip4/1.2.3.4/ipcidr/24": cidrOrFatal("1.2.3.4/24"),
22
+ "/ip4/192.168.0.0/ipcidr/28": cidrOrFatal("192.168.0.0/28"),
23
+ "/ip6/fe80::/ipcidr/0": cidrOrFatal("fe80::/0"),
24
+ "/ip6/fe80::/ipcidr/64": cidrOrFatal("fe80::/64"),
25
+ "/ip6/fe80::/ipcidr/128": cidrOrFatal("fe80::/128"),
26
+ }
27
+
28
+ for s, m1 := range testCases {
29
+ m2, err := NewMask(s)
30
+ if err != nil {
31
+ t.Error("should be invalid:", s)
32
+ continue
33
+ }
34
+
35
+ if m1.String() != m2.String() {
36
+ t.Error("masks not equal:", m1, m2)
37
+ }
38
+ }
39
+
40
+}
41
+
42
+func TestInvalidMasks(t *testing.T) {
43
+
44
+ testCases := []string{
45
+ "/",
46
+ "/ip4/10.1.2.3",
47
+ "/ip6/::",
48
+ "/ip4/1.2.3.4/cidr/24",
49
+ "/ip6/fe80::/cidr/24",
50
+ "/eth/aa:aa:aa:aa:aa/ipcidr/24",
51
+ "foobar/ip4/1.2.3.4/ipcidr/32",
52
+ }
53
+
54
+ for _, s := range testCases {
55
+ _, err := NewMask(s)
56
+ if err != ErrInvalidFormat {
57
+ t.Error("should be invalid:", s)
58
+ }
59
+ }
60
+
61
+ testCases2 := []string{
62
+ "/ip4/1.2.3.4/ipcidr/33",
63
+ "/ip4/192.168.0.0/ipcidr/-1",
64
+ "/ip6/fe80::/ipcidr/129",
65
+ }
66
+
67
+ for _, s := range testCases2 {
68
+ _, err := NewMask(s)
69
+ if err == nil {
70
+ t.Error("should be invalid:", s)
71
+ }
72
+ }
73
+
74
+}
75
+
76
func TestFiltered(t *testing.T) {
77
var tests = map[string]map[string]bool{
78
"/ip4/10.0.0.0/ipcidr/8": map[string]bool{
@@ -34,3 +102,31 @@ func TestFiltered(t *testing.T) {
102
}
103
}
104
}
105
+
106
+func TestParsing(t *testing.T) {
107
+ var addrs = map[string]string{
108
+ "/ip4/192.168.0.0/ipcidr/16": "192.168.0.0/16",
109
+ "/ip4/192.0.0.0/ipcidr/8": "192.0.0.0/8",
110
+ "/ip6/2001:db8::/ipcidr/32": "2001:db8::/32",
111
+ }
112
+
113
+ for k, v := range addrs {
114
+ m, err := NewMask(k)
115
+ if err != nil {
116
+ t.Fatal(err)
117
+ }
118
+
119
+ if m.String() != v {
120
+ t.Fatalf("mask is wrong: ", m, v)
121
+ }
122
+
123
+ orig, err := ConvertIPNet(m)
124
+ if err != nil {
125
+ t.Fatal(err)
126
+ }
127
+
128
+ if orig != k {
129
+ t.Fatal("backwards conversion failed: ", orig, k)
130
+ }
131
+ }
132
+}
core/commands/swarm.go
+143
@@ -9,10 +9,12 @@ import (
9
"sort"
10
11
cmds "github.com/ipfs/go-ipfs/commands"
12
+ swarm "github.com/ipfs/go-ipfs/p2p/net/swarm"
13
peer "github.com/ipfs/go-ipfs/p2p/peer"
14
iaddr "github.com/ipfs/go-ipfs/util/ipfsaddr"
15
16
ma "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr"
17
+ mafilter "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/whyrusleeping/multiaddr-filter"
18
context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"
19
)
20
@@ -32,6 +34,7 @@ ipfs swarm peers - List peers with open connections
34
ipfs swarm addrs - List known addresses. Useful to debug.
35
ipfs swarm connect <address> - Open connection to a given address
36
ipfs swarm disconnect <address> - Close connection to a given address
37
+ipfs swarm filters - Manipulate filters addresses
38
`,
39
ShortDescription: `
40
ipfs swarm is a tool to manipulate the network swarm. The swarm is the
@@ -44,6 +47,7 @@ ipfs peers in the internet.
47
"addrs": swarmAddrsCmd,
48
"connect": swarmConnectCmd,
49
"disconnect": swarmDisconnectCmd,
50
+ "filters": swarmFiltersCmd,
51
},
52
}
53
@@ -358,3 +362,142 @@ func peersWithAddresses(addrs []string) (pis []peer.PeerInfo, err error) {
362
}
363
return pis, nil
364
}
365
+
366
+var swarmFiltersCmd = &cmds.Command{
367
+ Helptext: cmds.HelpText{
368
+ Tagline: "Manipulate address filters",
369
+ ShortDescription: `
370
+'ipfs swarm filters' will list out currently applied filters. Its subcommands can be used
371
+to add or remove said filters. Filters are specified using the multiaddr-filter format:
372
+
373
+example:
374
+
375
+ /ip4/192.168.0.0/ipcidr/16
376
+
377
+Where the above is equivalent to the standard CIDR:
378
+
379
+ 192.168.0.0/16
380
+
381
+Filters default to those specified under the "DialBlocklist" config key.
382
+`,
383
+ },
384
+ Subcommands: map[string]*cmds.Command{
385
+ "add": swarmFiltersAddCmd,
386
+ "rm": swarmFiltersRmCmd,
387
+ },
388
+ Run: func(req cmds.Request, res cmds.Response) {
389
+ n, err := req.Context().GetNode()
390
+ if err != nil {
391
+ res.SetError(err, cmds.ErrNormal)
392
+ return
393
+ }
394
+
395
+ snet, ok := n.PeerHost.Network().(*swarm.Network)
396
+ if !ok {
397
+ res.SetError(errors.New("failed to cast network to swarm network"), cmds.ErrNormal)
398
+ return
399
+ }
400
+
401
+ var output []string
402
+ for _, f := range snet.Filters.Filters() {
403
+ s, err := mafilter.ConvertIPNet(f)
404
+ if err != nil {
405
+ res.SetError(err, cmds.ErrNormal)
406
+ return
407
+ }
408
+ output = append(output, s)
409
+ }
410
+ res.SetOutput(&stringList{output})
411
+ },
412
+ Marshalers: cmds.MarshalerMap{
413
+ cmds.Text: stringListMarshaler,
414
+ },
415
+ Type: stringList{},
416
+}
417
+
418
+var swarmFiltersAddCmd = &cmds.Command{
419
+ Helptext: cmds.HelpText{
420
+ Tagline: "add an address filter",
421
+ ShortDescription: `
422
+'ipfs swarm filters add' will add an address filter to the daemons swarm.
423
+Filters applied this way will not persist daemon reboots, to acheive that,
424
+add your filters to the ipfs config file.
425
+`,
426
+ },
427
+ Arguments: []cmds.Argument{
428
+ cmds.StringArg("address", true, true, "multiaddr to filter").EnableStdin(),
429
+ },
430
+ Run: func(req cmds.Request, res cmds.Response) {
431
+ n, err := req.Context().GetNode()
432
+ if err != nil {
433
+ res.SetError(err, cmds.ErrNormal)
434
+ return
435
+ }
436
+
437
+ snet, ok := n.PeerHost.Network().(*swarm.Network)
438
+ if !ok {
439
+ res.SetError(errors.New("failed to cast network to swarm network"), cmds.ErrNormal)
440
+ return
441
+ }
442
+
443
+ for _, arg := range req.Arguments() {
444
+ mask, err := mafilter.NewMask(arg)
445
+ if err != nil {
446
+ res.SetError(err, cmds.ErrNormal)
447
+ return
448
+ }
449
+
450
+ snet.Filters.AddDialFilter(mask)
451
+ }
452
+ },
453
+}
454
+
455
+var swarmFiltersRmCmd = &cmds.Command{
456
+ Helptext: cmds.HelpText{
457
+ Tagline: "remove an address filter",
458
+ ShortDescription: `
459
+'ipfs swarm filters rm' will remove an address filter from the daemons swarm.
460
+Filters removed this way will not persist daemon reboots, to acheive that,
461
+remove your filters from the ipfs config file.
462
+`,
463
+ },
464
+ Arguments: []cmds.Argument{
465
+ cmds.StringArg("address", true, true, "multiaddr filter to remove").EnableStdin(),
466
+ },
467
+ Run: func(req cmds.Request, res cmds.Response) {
468
+ n, err := req.Context().GetNode()
469
+ if err != nil {
470
+ res.SetError(err, cmds.ErrNormal)
471
+ return
472
+ }
473
+
474
+ if n.PeerHost == nil {
475
+ res.SetError(errNotOnline, cmds.ErrNormal)
476
+ return
477
+ }
478
+
479
+ snet, ok := n.PeerHost.Network().(*swarm.Network)
480
+ if !ok {
481
+ res.SetError(errors.New("failed to cast network to swarm network"), cmds.ErrNormal)
482
+ return
483
+ }
484
+
485
+ if req.Arguments()[0] == "all" || req.Arguments()[0] == "*" {
486
+ fs := snet.Filters.Filters()
487
+ for _, f := range fs {
488
+ snet.Filters.Remove(f)
489
+ }
490
+ return
491
+ }
492
+
493
+ for _, arg := range req.Arguments() {
494
+ mask, err := mafilter.NewMask(arg)
495
+ if err != nil {
496
+ res.SetError(err, cmds.ErrNormal)
497
+ return
498
+ }
499
+
500
+ snet.Filters.Remove(mask)
501
+ }
502
+ },
503
+}
p2p/net/filter/filter.go
+20
-2
@@ -9,11 +9,17 @@ import (
9
)
10
11
type Filters struct {
12
- filters []*net.IPNet
12
+ filters map[string]*net.IPNet
13
+}
14
+
15
+func NewFilters() *Filters {
16
+ return &Filters{
17
+ filters: make(map[string]*net.IPNet),
18
+ }
19
}
20
21
func (fs *Filters) AddDialFilter(f *net.IPNet) {
16
- fs.filters = append(fs.filters, f)
22
+ fs.filters[f.String()] = f
23
}
24
25
func (f *Filters) AddrBlocked(a ma.Multiaddr) bool {
@@ -32,3 +38,15 @@ func (f *Filters) AddrBlocked(a ma.Multiaddr) bool {
38
}
39
return false
40
}
41
+
42
+func (f *Filters) Filters() []*net.IPNet {
43
+ var out []*net.IPNet
44
+ for _, ff := range f.filters {
45
+ out = append(out, ff)
46
+ }
47
+ return out
48
+}
49
+
50
+func (f *Filters) Remove(ff *net.IPNet) {
51
+ delete(f.filters, ff.String())
52
+}
p2p/net/swarm/swarm.go
+1
-1
@@ -84,7 +84,7 @@ func NewSwarm(ctx context.Context, listenAddrs []ma.Multiaddr,
84
dialT: DialTimeout,
85
notifs: make(map[inet.Notifiee]ps.Notifiee),
86
bwc: bwc,
87
- Filters: new(filter.Filters),
87
+ Filters: filter.NewFilters(),
88
}
89
90
// configure Swarm
test/sharness/t0141-addfilter.sh
new
+71
@@ -0,0 +1,71 @@
1
+#!/bin/sh
2
+#
3
+# Copyright (c) 2014 Jeromy Johnson
4
+# MIT Licensed; see the LICENSE file in this repository.
5
+#
6
+
7
+test_description="Test ipfs swarm command"
8
+
9
+AF1="/ip4/192.168.0.0/ipcidr/16"
10
+AF2="/ip4/127.0.0.0/ipcidr/8"
11
+AF3="/ip6/2008:bcd::/ipcidr/32"
12
+AF4="/ip4/172.16.0.0/ipcidr/12"
13
+
14
+. lib/test-lib.sh
15
+
16
+test_init_ipfs
17
+
18
+test_swarm_filter_cmd() {
19
+ printf "" > list_expected
20
+ for AF in "$@"
21
+ do
22
+ echo "$AF" >>list_expected
23
+ done
24
+
25
+ test_expect_success "'ipfs swarm filters' succeeds" '
26
+ ipfs swarm filters > list_actual
27
+ '
28
+
29
+ test_expect_success "'ipfs swarm filters' output looks good" '
30
+ test_sort_cmp list_actual list_expected
31
+ '
32
+}
33
+
34
+test_swarm_filters() {
35
+
36
+ ipfs swarm filters rm all
37
+
38
+ test_swarm_filter_cmd
39
+
40
+ test_expect_success "'ipfs swarm filter add' succeeds" '
41
+ ipfs swarm filters add $AF1 $AF2 $AF3
42
+ '
43
+
44
+ test_swarm_filter_cmd $AF1 $AF2 $AF3
45
+
46
+ test_expect_success "'ipfs swarm filter rm' succeeds" '
47
+ ipfs swarm filters rm $AF2 $AF3
48
+ '
49
+
50
+ test_swarm_filter_cmd $AF1
51
+
52
+ test_expect_success "'ipfs swarm filter add' succeeds" '
53
+ ipfs swarm filters add $AF4 $AF2
54
+ '
55
+
56
+ test_swarm_filter_cmd $AF1 $AF2 $AF4
57
+
58
+ test_expect_success "'ipfs swarm filter rm' succeeds" '
59
+ ipfs swarm filters rm $AF1 $AF2 $AF4
60
+ '
61
+
62
+ test_swarm_filter_cmd
63
+}
64
+
65
+test_launch_ipfs_daemon
66
+
67
+test_swarm_filters
68
+
69
+test_kill_ipfs_daemon
70
+
71
+test_done