add command to manipulate address filters and a sharness test for them

License: MIT
Signed-off-by: Jeromy <jeromyj@gmail.com>
This commit is contained in:
Jeromy 2015-06-30 18:25:34 -07:00
parent e7fd57f69a
commit 7cc73f7b86
9 changed files with 403 additions and 11 deletions

2
Godeps/Godeps.json generated
View File

@ -282,7 +282,7 @@
},
{
"ImportPath": "github.com/whyrusleeping/multiaddr-filter",
"Rev": "15837fcc356fddef27c634b0f6379b3b7f259114"
"Rev": "9e26222151125ecd3fc1fd190179b6bdd55f5608"
},
{
"ImportPath": "golang.org/x/crypto/blowfish",

View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015 Jeromy Johnson
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -0,0 +1,15 @@
# go-multiaddr-filter -- CIDR netmasks with multiaddr
This module creates very simple [multiaddr](https://github.com/jbenet/go-multiaddr) formatted cidr netmasks.
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.
## Usage
```go
import filter "github.com/whyrusleeping/multiaddr-filter"
filter.NewMask("/ip4/192.168.0.0/24") // ipv4
filter.NewMask("/ip6/fe80::/64") // ipv6
```

View File

@ -2,18 +2,46 @@ package mask
import (
"errors"
"fmt"
"net"
"strings"
manet "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr-net"
)
var ErrInvalidFormat = errors.New("invalid multiaddr-filter format")
func NewMask(a string) (*net.IPNet, error) {
parts := strings.Split(a, "/")
if len(parts) == 5 && parts[1] == "ip4" && parts[3] == "ipcidr" {
_, ipn, err := net.ParseCIDR(parts[2] + "/" + parts[4])
if err != nil {
return nil, err
}
return ipn, nil
if parts[0] != "" {
return nil, ErrInvalidFormat
}
return nil, errors.New("invalid format")
if len(parts) != 5 {
return nil, ErrInvalidFormat
}
// check it's a valid filter address. ip + cidr
isip := parts[1] == "ip4" || parts[1] == "ip6"
iscidr := parts[3] == "ipcidr"
if !isip || !iscidr {
return nil, ErrInvalidFormat
}
_, ipn, err := net.ParseCIDR(parts[2] + "/" + parts[4])
if err != nil {
return nil, err
}
return ipn, nil
}
func ConvertIPNet(n *net.IPNet) (string, error) {
addr, err := manet.FromIP(n.IP)
if err != nil {
return "", err
}
b, _ := n.Mask.Size()
return fmt.Sprintf("%s/ipcidr/%d", addr, b), nil
}

View File

@ -5,6 +5,74 @@ import (
"testing"
)
func TestValidMasks(t *testing.T) {
cidrOrFatal := func(s string) *net.IPNet {
_, ipn, err := net.ParseCIDR(s)
if err != nil {
t.Fatal(err)
}
return ipn
}
testCases := map[string]*net.IPNet{
"/ip4/1.2.3.4/ipcidr/0": cidrOrFatal("1.2.3.4/0"),
"/ip4/1.2.3.4/ipcidr/32": cidrOrFatal("1.2.3.4/32"),
"/ip4/1.2.3.4/ipcidr/24": cidrOrFatal("1.2.3.4/24"),
"/ip4/192.168.0.0/ipcidr/28": cidrOrFatal("192.168.0.0/28"),
"/ip6/fe80::/ipcidr/0": cidrOrFatal("fe80::/0"),
"/ip6/fe80::/ipcidr/64": cidrOrFatal("fe80::/64"),
"/ip6/fe80::/ipcidr/128": cidrOrFatal("fe80::/128"),
}
for s, m1 := range testCases {
m2, err := NewMask(s)
if err != nil {
t.Error("should be invalid:", s)
continue
}
if m1.String() != m2.String() {
t.Error("masks not equal:", m1, m2)
}
}
}
func TestInvalidMasks(t *testing.T) {
testCases := []string{
"/",
"/ip4/10.1.2.3",
"/ip6/::",
"/ip4/1.2.3.4/cidr/24",
"/ip6/fe80::/cidr/24",
"/eth/aa:aa:aa:aa:aa/ipcidr/24",
"foobar/ip4/1.2.3.4/ipcidr/32",
}
for _, s := range testCases {
_, err := NewMask(s)
if err != ErrInvalidFormat {
t.Error("should be invalid:", s)
}
}
testCases2 := []string{
"/ip4/1.2.3.4/ipcidr/33",
"/ip4/192.168.0.0/ipcidr/-1",
"/ip6/fe80::/ipcidr/129",
}
for _, s := range testCases2 {
_, err := NewMask(s)
if err == nil {
t.Error("should be invalid:", s)
}
}
}
func TestFiltered(t *testing.T) {
var tests = map[string]map[string]bool{
"/ip4/10.0.0.0/ipcidr/8": map[string]bool{
@ -34,3 +102,31 @@ func TestFiltered(t *testing.T) {
}
}
}
func TestParsing(t *testing.T) {
var addrs = map[string]string{
"/ip4/192.168.0.0/ipcidr/16": "192.168.0.0/16",
"/ip4/192.0.0.0/ipcidr/8": "192.0.0.0/8",
"/ip6/2001:db8::/ipcidr/32": "2001:db8::/32",
}
for k, v := range addrs {
m, err := NewMask(k)
if err != nil {
t.Fatal(err)
}
if m.String() != v {
t.Fatalf("mask is wrong: ", m, v)
}
orig, err := ConvertIPNet(m)
if err != nil {
t.Fatal(err)
}
if orig != k {
t.Fatal("backwards conversion failed: ", orig, k)
}
}
}

View File

@ -9,10 +9,12 @@ import (
"sort"
cmds "github.com/ipfs/go-ipfs/commands"
swarm "github.com/ipfs/go-ipfs/p2p/net/swarm"
peer "github.com/ipfs/go-ipfs/p2p/peer"
iaddr "github.com/ipfs/go-ipfs/util/ipfsaddr"
ma "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr"
mafilter "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/whyrusleeping/multiaddr-filter"
context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"
)
@ -32,6 +34,7 @@ ipfs swarm peers - List peers with open connections
ipfs swarm addrs - List known addresses. Useful to debug.
ipfs swarm connect <address> - Open connection to a given address
ipfs swarm disconnect <address> - Close connection to a given address
ipfs swarm filters - Manipulate filters addresses
`,
ShortDescription: `
ipfs swarm is a tool to manipulate the network swarm. The swarm is the
@ -44,6 +47,7 @@ ipfs peers in the internet.
"addrs": swarmAddrsCmd,
"connect": swarmConnectCmd,
"disconnect": swarmDisconnectCmd,
"filters": swarmFiltersCmd,
},
}
@ -358,3 +362,142 @@ func peersWithAddresses(addrs []string) (pis []peer.PeerInfo, err error) {
}
return pis, nil
}
var swarmFiltersCmd = &cmds.Command{
Helptext: cmds.HelpText{
Tagline: "Manipulate address filters",
ShortDescription: `
'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:
example:
/ip4/192.168.0.0/ipcidr/16
Where the above is equivalent to the standard CIDR:
192.168.0.0/16
Filters default to those specified under the "DialBlocklist" config key.
`,
},
Subcommands: map[string]*cmds.Command{
"add": swarmFiltersAddCmd,
"rm": swarmFiltersRmCmd,
},
Run: func(req cmds.Request, res cmds.Response) {
n, err := req.Context().GetNode()
if err != nil {
res.SetError(err, 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
}
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{
Tagline: "add an address filter",
ShortDescription: `
'ipfs swarm filters add' will add an address filter to the daemons swarm.
Filters applied this way will not persist daemon reboots, to acheive that,
add your filters to the ipfs config file.
`,
},
Arguments: []cmds.Argument{
cmds.StringArg("address", true, true, "multiaddr to filter").EnableStdin(),
},
Run: func(req cmds.Request, res cmds.Response) {
n, err := req.Context().GetNode()
if err != nil {
res.SetError(err, 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
}
for _, arg := range req.Arguments() {
mask, err := mafilter.NewMask(arg)
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}
snet.Filters.AddDialFilter(mask)
}
},
}
var swarmFiltersRmCmd = &cmds.Command{
Helptext: cmds.HelpText{
Tagline: "remove an address filter",
ShortDescription: `
'ipfs swarm filters rm' will remove an address filter from the daemons swarm.
Filters removed this way will not persist daemon reboots, to acheive that,
remove your filters from the ipfs config file.
`,
},
Arguments: []cmds.Argument{
cmds.StringArg("address", true, true, "multiaddr filter to remove").EnableStdin(),
},
Run: func(req cmds.Request, res cmds.Response) {
n, err := req.Context().GetNode()
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
}
if req.Arguments()[0] == "all" || req.Arguments()[0] == "*" {
fs := snet.Filters.Filters()
for _, f := range fs {
snet.Filters.Remove(f)
}
return
}
for _, arg := range req.Arguments() {
mask, err := mafilter.NewMask(arg)
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}
snet.Filters.Remove(mask)
}
},
}

View File

@ -9,11 +9,17 @@ import (
)
type Filters struct {
filters []*net.IPNet
filters map[string]*net.IPNet
}
func NewFilters() *Filters {
return &Filters{
filters: make(map[string]*net.IPNet),
}
}
func (fs *Filters) AddDialFilter(f *net.IPNet) {
fs.filters = append(fs.filters, f)
fs.filters[f.String()] = f
}
func (f *Filters) AddrBlocked(a ma.Multiaddr) bool {
@ -32,3 +38,15 @@ func (f *Filters) AddrBlocked(a ma.Multiaddr) bool {
}
return false
}
func (f *Filters) Filters() []*net.IPNet {
var out []*net.IPNet
for _, ff := range f.filters {
out = append(out, ff)
}
return out
}
func (f *Filters) Remove(ff *net.IPNet) {
delete(f.filters, ff.String())
}

View File

@ -84,7 +84,7 @@ func NewSwarm(ctx context.Context, listenAddrs []ma.Multiaddr,
dialT: DialTimeout,
notifs: make(map[inet.Notifiee]ps.Notifiee),
bwc: bwc,
Filters: new(filter.Filters),
Filters: filter.NewFilters(),
}
// configure Swarm

View File

@ -0,0 +1,71 @@
#!/bin/sh
#
# Copyright (c) 2014 Jeromy Johnson
# MIT Licensed; see the LICENSE file in this repository.
#
test_description="Test ipfs swarm command"
AF1="/ip4/192.168.0.0/ipcidr/16"
AF2="/ip4/127.0.0.0/ipcidr/8"
AF3="/ip6/2008:bcd::/ipcidr/32"
AF4="/ip4/172.16.0.0/ipcidr/12"
. lib/test-lib.sh
test_init_ipfs
test_swarm_filter_cmd() {
printf "" > list_expected
for AF in "$@"
do
echo "$AF" >>list_expected
done
test_expect_success "'ipfs swarm filters' succeeds" '
ipfs swarm filters > list_actual
'
test_expect_success "'ipfs swarm filters' output looks good" '
test_sort_cmp list_actual list_expected
'
}
test_swarm_filters() {
ipfs swarm filters rm all
test_swarm_filter_cmd
test_expect_success "'ipfs swarm filter add' succeeds" '
ipfs swarm filters add $AF1 $AF2 $AF3
'
test_swarm_filter_cmd $AF1 $AF2 $AF3
test_expect_success "'ipfs swarm filter rm' succeeds" '
ipfs swarm filters rm $AF2 $AF3
'
test_swarm_filter_cmd $AF1
test_expect_success "'ipfs swarm filter add' succeeds" '
ipfs swarm filters add $AF4 $AF2
'
test_swarm_filter_cmd $AF1 $AF2 $AF4
test_expect_success "'ipfs swarm filter rm' succeeds" '
ipfs swarm filters rm $AF1 $AF2 $AF4
'
test_swarm_filter_cmd
}
test_launch_ipfs_daemon
test_swarm_filters
test_kill_ipfs_daemon
test_done