diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 4ee07c958e3..426cabafd5c 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -239,6 +239,10 @@ "ImportPath": "github.com/whyrusleeping/iptb", "Rev": "3970c95a864f1a40037f796ff596607ce8ae43be" }, + { + "ImportPath": "github.com/whyrusleeping/multiaddr-filter", + "Rev": "15837fcc356fddef27c634b0f6379b3b7f259114" + }, { "ImportPath": "golang.org/x/crypto/blowfish", "Rev": "c84e1f8e3a7e322d497cd16c0e8a13c7e127baf3" diff --git a/Godeps/_workspace/src/github.com/whyrusleeping/multiaddr-filter/mask.go b/Godeps/_workspace/src/github.com/whyrusleeping/multiaddr-filter/mask.go new file mode 100644 index 00000000000..91208fe048d --- /dev/null +++ b/Godeps/_workspace/src/github.com/whyrusleeping/multiaddr-filter/mask.go @@ -0,0 +1,19 @@ +package mask + +import ( + "errors" + "net" + "strings" +) + +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 + } + return nil, errors.New("invalid format") +} diff --git a/Godeps/_workspace/src/github.com/whyrusleeping/multiaddr-filter/mask_test.go b/Godeps/_workspace/src/github.com/whyrusleeping/multiaddr-filter/mask_test.go new file mode 100644 index 00000000000..4507dca68f8 --- /dev/null +++ b/Godeps/_workspace/src/github.com/whyrusleeping/multiaddr-filter/mask_test.go @@ -0,0 +1,36 @@ +package mask + +import ( + "net" + "testing" +) + +func TestFiltered(t *testing.T) { + var tests = map[string]map[string]bool{ + "/ip4/10.0.0.0/ipcidr/8": map[string]bool{ + "10.3.3.4": true, + "10.3.4.4": true, + "10.4.4.4": true, + "15.52.34.3": false, + }, + "/ip4/192.168.0.0/ipcidr/16": map[string]bool{ + "192.168.0.0": true, + "192.168.1.0": true, + "192.1.0.0": false, + "10.4.4.4": false, + }, + } + + for mask, set := range tests { + m, err := NewMask(mask) + if err != nil { + t.Fatal(err) + } + for addr, val := range set { + ip := net.ParseIP(addr) + if m.Contains(ip) != val { + t.Fatalf("expected contains(%s, %s) == %s", mask, addr, val) + } + } + } +} diff --git a/core/core.go b/core/core.go index 877ffe9c0f7..e3111a17274 100644 --- a/core/core.go +++ b/core/core.go @@ -13,17 +13,17 @@ import ( "errors" "fmt" "io" + "net" "time" b58 "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-base58" ctxgroup "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-ctxgroup" ds "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-datastore" ma "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr" + mamask "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" - metrics "github.com/ipfs/go-ipfs/metrics" - eventlog "github.com/ipfs/go-ipfs/thirdparty/eventlog" - diag "github.com/ipfs/go-ipfs/diagnostics" + metrics "github.com/ipfs/go-ipfs/metrics" ic "github.com/ipfs/go-ipfs/p2p/crypto" discovery "github.com/ipfs/go-ipfs/p2p/discovery" p2phost "github.com/ipfs/go-ipfs/p2p/host" @@ -32,6 +32,7 @@ import ( swarm "github.com/ipfs/go-ipfs/p2p/net/swarm" addrutil "github.com/ipfs/go-ipfs/p2p/net/swarm/addr" peer "github.com/ipfs/go-ipfs/p2p/peer" + eventlog "github.com/ipfs/go-ipfs/thirdparty/eventlog" routing "github.com/ipfs/go-ipfs/routing" dht "github.com/ipfs/go-ipfs/routing/dht" @@ -254,7 +255,18 @@ func (n *IpfsNode) startOnlineServices(ctx context.Context, routingOption Routin // Set reporter n.Reporter = metrics.NewBandwidthCounter() - peerhost, err := hostOption(ctx, n.Identity, n.Peerstore, n.Reporter) + // get undialable addrs from config + cfg := n.Repo.Config() + var addrfilter []*net.IPNet + for _, s := range cfg.DialBlocklist { + f, err := mamask.NewMask(s) + if err != nil { + return fmt.Errorf("incorrectly formatter address filter in config: %s", s) + } + addrfilter = append(addrfilter, f) + } + + peerhost, err := hostOption(ctx, n.Identity, n.Peerstore, n.Reporter, addrfilter) if err != nil { return err } @@ -508,12 +520,12 @@ func listenAddresses(cfg *config.Config) ([]ma.Multiaddr, error) { return listen, nil } -type HostOption func(ctx context.Context, id peer.ID, ps peer.Peerstore, bwr metrics.Reporter) (p2phost.Host, error) +type HostOption func(ctx context.Context, id peer.ID, ps peer.Peerstore, bwr metrics.Reporter, fs []*net.IPNet) (p2phost.Host, error) var DefaultHostOption HostOption = constructPeerHost // isolates the complex initialization steps -func constructPeerHost(ctx context.Context, id peer.ID, ps peer.Peerstore, bwr metrics.Reporter) (p2phost.Host, error) { +func constructPeerHost(ctx context.Context, id peer.ID, ps peer.Peerstore, bwr metrics.Reporter, fs []*net.IPNet) (p2phost.Host, error) { // no addresses to begin with. we'll start later. network, err := swarm.NewNetwork(ctx, nil, id, ps, bwr) diff --git a/p2p/net/conn/interface.go b/p2p/net/conn/interface.go index 7d2c95af102..3a61911af82 100644 --- a/p2p/net/conn/interface.go +++ b/p2p/net/conn/interface.go @@ -7,6 +7,7 @@ import ( key "github.com/ipfs/go-ipfs/blocks/key" ic "github.com/ipfs/go-ipfs/p2p/crypto" + filter "github.com/ipfs/go-ipfs/p2p/net/filter" peer "github.com/ipfs/go-ipfs/p2p/peer" msgio "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-msgio" @@ -86,6 +87,8 @@ type Listener interface { // LocalPeer is the identity of the local Peer. LocalPeer() peer.ID + SetAddrFilters(*filter.Filters) + // Close closes the listener. // Any blocked Accept operations will be unblocked and return errors. Close() error diff --git a/p2p/net/conn/listen.go b/p2p/net/conn/listen.go index d60c0ba3ad0..ea91e5a56d4 100644 --- a/p2p/net/conn/listen.go +++ b/p2p/net/conn/listen.go @@ -13,6 +13,7 @@ import ( context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context" ic "github.com/ipfs/go-ipfs/p2p/crypto" + filter "github.com/ipfs/go-ipfs/p2p/net/filter" peer "github.com/ipfs/go-ipfs/p2p/peer" ) @@ -26,6 +27,8 @@ type listener struct { local peer.ID // LocalPeer is the identity of the local Peer privk ic.PrivKey // private key to use to initialize secure conns + filters *filter.Filters + wrapper ConnWrapper cg ctxgroup.ContextGroup @@ -45,6 +48,10 @@ func (l *listener) String() string { return fmt.Sprintf("", l.local, l.Multiaddr()) } +func (l *listener) SetAddrFilters(fs *filter.Filters) { + l.filters = fs +} + // Accept waits for and returns the next connection to the listener. // Note that unfortunately this func (l *listener) Accept() (net.Conn, error) { @@ -81,6 +88,12 @@ func (l *listener) Accept() (net.Conn, error) { } log.Debugf("listener %s got connection: %s <---> %s", l, maconn.LocalMultiaddr(), maconn.RemoteMultiaddr()) + + if l.filters != nil && l.filters.AddrBlocked(maconn.RemoteMultiaddr()) { + log.Debugf("blocked connection from %s", maconn.RemoteMultiaddr()) + maconn.Close() + continue + } // If we have a wrapper func, wrap this conn if l.wrapper != nil { maconn = l.wrapper(maconn) diff --git a/p2p/net/filter/filter.go b/p2p/net/filter/filter.go new file mode 100644 index 00000000000..1642a8b77c7 --- /dev/null +++ b/p2p/net/filter/filter.go @@ -0,0 +1,34 @@ +package filter + +import ( + "net" + "strings" + + ma "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr" + manet "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr-net" +) + +type Filters struct { + filters []*net.IPNet +} + +func (fs *Filters) AddDialFilter(f *net.IPNet) { + fs.filters = append(fs.filters, f) +} + +func (f *Filters) AddrBlocked(a ma.Multiaddr) bool { + _, addr, err := manet.DialArgs(a) + if err != nil { + // if we cant parse it, its probably not blocked + return false + } + + ipstr := strings.Split(addr, ":")[0] + ip := net.ParseIP(ipstr) + for _, ft := range f.filters { + if ft.Contains(ip) { + return true + } + } + return false +} diff --git a/p2p/net/swarm/swarm.go b/p2p/net/swarm/swarm.go index 15a5bbddbdf..4fe45df8549 100644 --- a/p2p/net/swarm/swarm.go +++ b/p2p/net/swarm/swarm.go @@ -9,6 +9,7 @@ import ( metrics "github.com/ipfs/go-ipfs/metrics" inet "github.com/ipfs/go-ipfs/p2p/net" + filter "github.com/ipfs/go-ipfs/p2p/net/filter" addrutil "github.com/ipfs/go-ipfs/p2p/net/swarm/addr" peer "github.com/ipfs/go-ipfs/p2p/peer" eventlog "github.com/ipfs/go-ipfs/thirdparty/eventlog" @@ -50,6 +51,9 @@ type Swarm struct { notifmu sync.RWMutex notifs map[inet.Notifiee]ps.Notifiee + // filters for addresses that shouldnt be dialed + Filters *filter.Filters + cg ctxgroup.ContextGroup bwc metrics.Reporter } @@ -64,13 +68,14 @@ func NewSwarm(ctx context.Context, listenAddrs []ma.Multiaddr, } s := &Swarm{ - swarm: ps.NewSwarm(PSTransport), - local: local, - peers: peers, - cg: ctxgroup.WithContext(ctx), - dialT: DialTimeout, - notifs: make(map[inet.Notifiee]ps.Notifiee), - bwc: bwc, + swarm: ps.NewSwarm(PSTransport), + local: local, + peers: peers, + cg: ctxgroup.WithContext(ctx), + dialT: DialTimeout, + notifs: make(map[inet.Notifiee]ps.Notifiee), + bwc: bwc, + Filters: new(filter.Filters), } // configure Swarm diff --git a/p2p/net/swarm/swarm_dial.go b/p2p/net/swarm/swarm_dial.go index aacee6ec440..c969ca57018 100644 --- a/p2p/net/swarm/swarm_dial.go +++ b/p2p/net/swarm/swarm_dial.go @@ -303,6 +303,7 @@ func (s *Swarm) dial(ctx context.Context, p peer.ID) (*Conn, error) { ila, _ := s.InterfaceListenAddresses() remoteAddrs = addrutil.Subtract(remoteAddrs, ila) remoteAddrs = addrutil.Subtract(remoteAddrs, s.peers.Addrs(s.local)) + log.Debugf("%s swarm dialing %s -- local:%s remote:%s", s.local, p, s.ListenAddresses(), remoteAddrs) if len(remoteAddrs) == 0 { err := errors.New("peer has no addresses") @@ -310,6 +311,13 @@ func (s *Swarm) dial(ctx context.Context, p peer.ID) (*Conn, error) { return nil, err } + remoteAddrs = s.filterAddrs(remoteAddrs) + if len(remoteAddrs) == 0 { + err := errors.New("all adresses for peer have been filtered out") + logdial["error"] = err + return nil, err + } + // open connection to peer d := &conn.Dialer{ Dialer: manet.Dialer{ @@ -454,6 +462,16 @@ func (s *Swarm) dialAddr(ctx context.Context, d *conn.Dialer, p peer.ID, addr ma return connC, nil } +func (s *Swarm) filterAddrs(addrs []ma.Multiaddr) []ma.Multiaddr { + var out []ma.Multiaddr + for _, a := range addrs { + if !s.Filters.AddrBlocked(a) { + out = append(out, a) + } + } + return out +} + // dialConnSetup is the setup logic for a connection from the dial side. it // needs to add the Conn to the StreamSwarm, then run newConnSetup func dialConnSetup(ctx context.Context, s *Swarm, connC conn.Conn) (*Conn, error) { diff --git a/p2p/net/swarm/swarm_listen.go b/p2p/net/swarm/swarm_listen.go index 2a985763f1b..4a8f4dd4d9e 100644 --- a/p2p/net/swarm/swarm_listen.go +++ b/p2p/net/swarm/swarm_listen.go @@ -69,6 +69,8 @@ func (s *Swarm) setupListener(maddr ma.Multiaddr) error { return err } + list.SetAddrFilters(s.Filters) + if cw, ok := list.(conn.ListenerConnWrapper); ok { cw.SetConnWrapper(func(c manet.Conn) manet.Conn { return mconn.WrapConn(s.bwc, c) diff --git a/p2p/net/swarm/swarm_test.go b/p2p/net/swarm/swarm_test.go index 9aa825320ab..4c12a1b5d3b 100644 --- a/p2p/net/swarm/swarm_test.go +++ b/p2p/net/swarm/swarm_test.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "io" + "net" "sync" "testing" "time" @@ -270,3 +271,60 @@ func TestConnHandler(t *testing.T) { default: } } + +func TestAddrBlocking(t *testing.T) { + ctx := context.Background() + swarms := makeSwarms(ctx, t, 2) + + swarms[0].SetConnHandler(func(conn *Conn) { + t.Fatal("no connections should happen!") + }) + + _, block, err := net.ParseCIDR("127.0.0.1/8") + if err != nil { + t.Fatal(err) + } + + swarms[1].Filters.AddDialFilter(block) + + swarms[1].peers.AddAddr(swarms[0].LocalPeer(), swarms[0].ListenAddresses()[0], peer.PermanentAddrTTL) + _, err = swarms[1].Dial(context.TODO(), swarms[0].LocalPeer()) + if err == nil { + t.Fatal("dial should have failed") + } + + swarms[0].peers.AddAddr(swarms[1].LocalPeer(), swarms[1].ListenAddresses()[0], peer.PermanentAddrTTL) + _, err = swarms[0].Dial(context.TODO(), swarms[1].LocalPeer()) + if err == nil { + t.Fatal("dial should have failed") + } +} + +func TestFilterBounds(t *testing.T) { + ctx := context.Background() + swarms := makeSwarms(ctx, t, 2) + + conns := make(chan struct{}, 8) + swarms[0].SetConnHandler(func(conn *Conn) { + conns <- struct{}{} + }) + + // Address that we wont be dialing from + _, block, err := net.ParseCIDR("192.0.0.1/8") + if err != nil { + t.Fatal(err) + } + + // set filter on both sides, shouldnt matter + swarms[1].Filters.AddDialFilter(block) + swarms[0].Filters.AddDialFilter(block) + + connectSwarms(t, ctx, swarms) + + select { + case <-time.After(time.Second): + t.Fatal("should have gotten connection") + case <-conns: + fmt.Println("got connect") + } +} diff --git a/repo/config/config.go b/repo/config/config.go index c9a8aabec68..e7474cf7e09 100644 --- a/repo/config/config.go +++ b/repo/config/config.go @@ -26,6 +26,7 @@ type Config struct { Tour Tour // local node's tour position Gateway Gateway // local node's gateway server options SupernodeRouting SupernodeClientConfig // local node's routing servers (if SupernodeRouting enabled) + DialBlocklist []string Log Log } diff --git a/util/sadhack/godep.go b/util/sadhack/godep.go index c38bbb1e4a0..47a2350bb90 100644 --- a/util/sadhack/godep.go +++ b/util/sadhack/godep.go @@ -6,4 +6,5 @@ import _ "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/dustin/go-hum // similar to the above, only used in the tests makefile import _ "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/whyrusleeping/iptb" + import _ "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/chriscool/go-sleep"